/*
* Copyright 2013, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.betfair.cougar.testing;
import com.betfair.cougar.api.ContainerContext;
import com.betfair.cougar.api.RequestContext;
import com.betfair.cougar.api.ResponseCode;
import com.betfair.cougar.caching.CacheFrameworkIntegration;
import com.betfair.cougar.caching.CacheFrameworkRegistry;
import com.betfair.cougar.core.api.ev.TimeConstraints;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.betfair.testingservice.v1.TestingService;
import com.betfair.testingservice.v1.enumerations.TestingExceptionErrorCodeEnum;
import com.betfair.testingservice.v1.exception.TestingException;
import com.betfair.testingservice.v1.to.CallResponse;
import com.betfair.testingservice.v1.to.IDD;
import com.betfair.testingservice.v1.to.LogFileResponse;
import com.betfair.tornjak.monitor.*;
import org.springframework.jmx.export.annotation.ManagedResource;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
@ManagedResource
public class TestingServiceImpl implements TestingService {
final static Logger LOGGER = LoggerFactory
.getLogger(TestingServiceImpl.class);
private CacheFrameworkRegistry cacheFrameworkRegistry;
private String baseLogDirectory = null;
private boolean doSkipLogLines = false;
private long maxMessageSize = 500;
private long defaultMaxNumberOfResults = Long.MAX_VALUE;
private String logDateTimeFormat = null;
public void setCacheFrameworkRegistry(CacheFrameworkRegistry cacheFrameworkRegistry) {
this.cacheFrameworkRegistry = cacheFrameworkRegistry;
}
public String getLogDateTimeFormat() {
return logDateTimeFormat;
}
public void setLogDateTimeFormat(String logDateTimeFormat) {
this.logDateTimeFormat = logDateTimeFormat;
}
public long getDefaultMaxNumberOfResults() {
return defaultMaxNumberOfResults;
}
public void setDefaultMaxNumberOfResults(long defaultMaxNumberOfResults) {
this.defaultMaxNumberOfResults = defaultMaxNumberOfResults;
}
public void setDoSkipLogLines(boolean doSkipLogLines) {
this.doSkipLogLines = doSkipLogLines;
}
public void setMaxMessageSize(long maxMessageSize) {
this.maxMessageSize = maxMessageSize;
}
private boolean isDoSkipLogLines() {
return doSkipLogLines;
}
private long getMaxMessageSize() {
return maxMessageSize;
}
// injected via Spring
public void setBaseLogDirectory(String logDirectory) {
this.baseLogDirectory = logDirectory;
}
public String getBaseLogDirectory() {
return this.baseLogDirectory;
}
@Override
public void init(ContainerContext cc) {
}
@Override
public CallResponse refreshCache(RequestContext ctx, String name, TimeConstraints timeConstraints)
throws TestingException {
CallResponse response = new CallResponse();
boolean found = false;
for (CacheFrameworkIntegration framework : cacheFrameworkRegistry.getFrameworks()) {
if (framework.refreshNamedCache(name)) {
found = true;
}
}
if (!found) {
throw new TestingException(ResponseCode.NotFound,
TestingExceptionErrorCodeEnum.NOT_FOUND);
} else {
response.setResult("OK");
}
return response;
}
@Override
public IDD getIDD(RequestContext ctx, String name, TimeConstraints timeConstraints) throws TestingException {
InputStream iddStream = getClass().getResourceAsStream("/idd/" + name);
LOGGER.debug("Retriving IDD {}", "/idd/" + name);
if (iddStream != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(
iddStream));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
throw new TestingException(e, ResponseCode.InternalError,
TestingExceptionErrorCodeEnum.GENERIC);
} finally {
try {
reader.close();
} catch (IOException e) {
LOGGER.warn( "Failed to close input stream",
e);
// Swallow it in case the it overrides the main exception
}
}
IDD idd = new IDD();
idd.setName(name);
idd.setContent(sb.toString());
return idd;
}
LOGGER.debug("IDD {} not found", "idd/" + name);
throw new TestingException(ResponseCode.NotFound,
TestingExceptionErrorCodeEnum.NOT_FOUND);
}
@Override
public CallResponse refreshAllCaches(RequestContext ctx, TimeConstraints timeConstraints)
throws TestingException {
CallResponse response = new CallResponse();
boolean done = false;
for (CacheFrameworkIntegration framework : cacheFrameworkRegistry.getFrameworks()) {
done = true;
framework.refreshAllCaches();
}
if (done) {
LOGGER.info("Refreshed all caches");
response.setResult("OK");
return response;
}
LOGGER.info("No cache frameworks found");
throw new TestingException(ResponseCode.InternalError,
TestingExceptionErrorCodeEnum.GENERIC);
}
@Override
public LogFileResponse getLogEntriesByDateRange(RequestContext ctx,
String logFileName, String startDateTime, String endDateTime, TimeConstraints timeConstraints)
throws TestingException {
List<String> logLines = null;
String physicalLogFileName = this.getBaseLogDirectory() + logFileName;
String logMessage = "Request for " + startDateTime + " to "
+ endDateTime + " of logfile " + physicalLogFileName;
LOGGER.info(logMessage);
List<LogEntryCondition> conditions = new ArrayList<LogEntryCondition>();
if (startDateTime != null) {
StartDateTimeLogEntryCondition startCond = new StartDateTimeLogEntryCondition(
this.getLogDateTimeFormat());
startCond.setCheckDate(startDateTime);
if (startCond.getCheckDate() != null) {
conditions.add(startCond);
} else {
LOGGER.info("Couldn't parse " + startDateTime
+ " to date");
}
}
if (endDateTime != null) {
EndDateTimeLogEntryCondition endCond = new EndDateTimeLogEntryCondition(
this.getLogDateTimeFormat());
endCond.setCheckDate(endDateTime);
if (endCond.getCheckDate() != null) {
conditions.add(endCond);
} else {
LOGGER.info("Couldn't parse " + endDateTime
+ " to date");
}
}
logLines = getLogFilesConditionalImpl(physicalLogFileName, conditions,
this.getDefaultMaxNumberOfResults());
LogFileResponse lfr = new LogFileResponse();
lfr.setResult(logLines);
return lfr;
}
// this method does a short cut implementation - it can skip much of the
// start of the file
@Override
public LogFileResponse getLogEntries(RequestContext ctx, String logFileName,
Integer numberOfLines, TimeConstraints timeConstraints) throws TestingException {
long start = System.nanoTime();
List<String> logLines = null;
String physicalLogFileName = this.getBaseLogDirectory() + logFileName;
StringBuilder logMessageBuilder = new StringBuilder("Request for ");
logMessageBuilder.append(numberOfLines.intValue());
logMessageBuilder.append(" lines of logfile ");
logMessageBuilder.append(physicalLogFileName);
LOGGER.info(logMessageBuilder.toString());
LOGGER.debug("Do skip lines : " + this.isDoSkipLogLines());
LOGGER.debug("Max entry size : " + this.getMaxMessageSize());
logLines = getLogFilesSkippingReader(physicalLogFileName, numberOfLines
.intValue());
long end = System.nanoTime();
LOGGER.info("Took " + (end - start) + " ns");
LogFileResponse lfr = new LogFileResponse();
lfr.setResult(logLines);
return lfr;
}
/**
* getLogFilesConditionalImpl does a generic search of log file using a set
* of conditions Simple implementation using a rolling buffer and FileReader
*
* @param filename
* @param conditions
* @param maxEntriesReturned
* @return List of log file entries
* @throws TestingException
*/
private List<String> getLogFilesConditionalImpl(String filename,
final List<LogEntryCondition> conditions, long maxEntriesReturned)
throws TestingException {
List<String> loglineBuffer = null;
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filename));
loglineBuffer = getLogFilesBufferedReaderImpl(br, conditions, maxEntriesReturned);
} catch (IOException iox) {
LOGGER.error("",iox);
throw new TestingException(iox,ResponseCode.NotFound,
TestingExceptionErrorCodeEnum.NOT_FOUND);
} finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
LOGGER.warn("Error while closing file "+filename, e);
}
}
}
return loglineBuffer;
}
// note - if there are no conditions it automatically matches...
private boolean allConditionsMatch(String logLine,
final List<LogEntryCondition> conditions) {
boolean allMatch = true;
if (conditions != null) {
Iterator<LogEntryCondition> iter = conditions.iterator();
while (iter.hasNext()) {
if (((LogEntryCondition)iter.next()).matchesEntry(logLine) == false) {
allMatch = false;
break;
}
}
}
return allMatch;
}
/**
* Simple implementation of moving "window" buffer
* Reads file from the start, keeps last x entries that match conditions
*
* @param reader
* @param conditions
* @param maxEntriesReturned
* @return
* @throws IOException
*/
private List<String> getLogFilesBufferedReaderImpl(BufferedReader reader,
final List<LogEntryCondition> conditions, long maxEntriesReturned)
throws IOException {
// rolling buffer of last #maxEntriesReturned# log entries that match
LinkedList<String> loglineBuffer = new LinkedList<String>();
if (reader != null) {
int currentSize = 0;
long excessRows = 0;
String thisLine;
while ((thisLine = reader.readLine()) != null) {
if (allConditionsMatch(thisLine, conditions)) {
LOGGER.debug("All conditions match " + thisLine);
if (currentSize >= maxEntriesReturned) {
loglineBuffer.removeFirst();
excessRows++;
}
loglineBuffer.addLast(thisLine);
currentSize = loglineBuffer.size();
}
}
LOGGER.info(excessRows
+ " rows ejected due to size limit");
}
return loglineBuffer;
}
/**
* getLogFilesSkippingReader Special case where it just wants last n rows of
* the logfile So can skip a large chunk of it Simple implementation using
* FileReader and rolling buffer
*
* @param filename
* @param numLines
* @return
* @throws TestingException
*/
private List<String> getLogFilesSkippingReader(String filename, int numLines)
throws TestingException {
List<String> loglineBuffer = null;
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filename));
// should do initial skip if file is miles larger than wanted log
// lines
if (this.isDoSkipLogLines()) {
File rawFile = new File(filename);
long fileSize = rawFile.length();
rawFile = null;
long likelyLength = numLines * this.getMaxMessageSize() * 2;
if (fileSize > likelyLength) {
long skipSize = fileSize - likelyLength;
if (skipSize < fileSize) {
LOGGER.info("Skipping " + skipSize
+ " in file");
br.skip(skipSize);
}
}
}
loglineBuffer = getLogFilesBufferedReaderImpl(br, null, numLines);
} catch (IOException iox) {
LOGGER.error("",iox);
throw new TestingException(iox,ResponseCode.NotFound,
TestingExceptionErrorCodeEnum.NOT_FOUND);
} finally {
if(br != null) {
try {
br.close();
} catch(IOException ex) {
LOGGER.warn("Error while closing file "+filename, ex);
}
}
}
return loglineBuffer;
}
}