package org.epics.archiverappliance.retrieval;
import java.io.IOException;
import java.net.URLEncoder;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentSkipListSet;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.Event;
import org.epics.archiverappliance.StoragePlugin;
import org.epics.archiverappliance.common.BasicContext;
import org.epics.archiverappliance.common.TimeUtils;
import org.epics.archiverappliance.config.ApplianceInfo;
import org.epics.archiverappliance.config.ChannelArchiverDataServerPVInfo;
import org.epics.archiverappliance.config.ConfigService;
import org.epics.archiverappliance.config.PVTypeInfo;
import org.epics.archiverappliance.config.StoragePluginURLParser;
import org.epics.archiverappliance.mgmt.policy.PolicyConfig.SamplingMethod;
public class RetrievalState {
private static Logger logger = Logger.getLogger(RetrievalState.class.getName());
private ConfigService configService;
private int engineWriteThreadInSeconds = 60;
public RetrievalState(ConfigService configService) {
this.configService = configService;
this.engineWriteThreadInSeconds = Integer.parseInt(configService.getInstallationProperties().getProperty("org.epics.archiverappliance.config.PVTypeInfo.secondsToBuffer", "60"));
}
/**
* Get the data sources for a PV in the order of their lifetime id...
* @param context BasicContext
* @param pvName The name of PV.
* @param typeInfo PVTypeInfo
* @param start Timestamp
* @param end Timestamp
* @param req HttpServletRequest
* @return the data source for a PV
* @throws IOException
*/
public List<DataSourceforPV> getDataSources(BasicContext context, String pvName, PVTypeInfo typeInfo, Timestamp start, Timestamp end, HttpServletRequest req) throws IOException {
if(typeInfo == null) {
List<ChannelArchiverDataServerPVInfo> caServers = this.configService.getChannelArchiverDataServers(pvName);
if(caServers != null) {
ArrayList<DataSourceforPV> dataSourcesForPV = new ArrayList<DataSourceforPV>();
for(ChannelArchiverDataServerPVInfo caServer : caServers) {
int count = determineCount(req);
String howStr = determineHowStr(req);
int lifetimeid = 1;
logger.debug("Adding Channel Archiver server for " + pvName + " " + caServer.toString());
dataSourcesForPV.add(new DataSourceforPV(pvName, caServer.getServerInfo().getPlugin(count, howStr), lifetimeid++, null, null));
return dataSourcesForPV;
}
}
return null;
}
try {
ConcurrentSkipListSet<DataSourceforPV> dataSourcesForPV = new ConcurrentSkipListSet<DataSourceforPV>();
// Add the engine.
// We only add the engine if the end time justifies us going to the engine.
// And also we skip if the typeinfo is a manufactured one based on the Sampling Method
long currentEpochSeconds = TimeUtils.getCurrentEpochSeconds();
long endEpochSeconds = TimeUtils.convertToEpochSeconds(end);
if((endEpochSeconds >= currentEpochSeconds) || ((currentEpochSeconds - endEpochSeconds) < 2 * engineWriteThreadInSeconds)) {
if(typeInfo.getSamplingMethod() == SamplingMethod.DONT_ARCHIVE) {
logger.debug("Skipping going to the engine for something we are not sampling for pv " + pvName);
} else {
ApplianceInfo applianceInfo = configService.getAppliance(typeInfo.getApplianceIdentity());
String engineRawURL = URLEncoder.encode(applianceInfo.getEngineURL() + "/getData.raw", "UTF-8");
StoragePlugin engineStoragePlugin = StoragePluginURLParser.parseStoragePlugin("pbraw://localhost?rawURL=" + engineRawURL + "&name=engine", configService);
dataSourcesForPV.add(new DataSourceforPV(pvName, engineStoragePlugin, 0, null, null));
}
} else {
logger.debug("Skipping going to the engine for data " + TimeUtils.convertToISO8601String(currentEpochSeconds) + "/" + TimeUtils.convertToISO8601String(endEpochSeconds));
}
// Add the various storage plugins
int lifetimeid = 1;
for(String store : typeInfo.getDataStores()) {
StoragePlugin storagePlugin = StoragePluginURLParser.parseStoragePlugin(store, configService);
dataSourcesForPV.add(new DataSourceforPV(pvName, storagePlugin, lifetimeid++, null, null));
Event firstKnownEvent = storagePlugin.getFirstKnownEvent(context, pvName);
if(firstKnownEvent != null && firstKnownEvent.getEventTimeStamp().before(start)) {
logger.info("Found a data source " + storagePlugin.getName() + " that has an event " + TimeUtils.convertToISO8601String(firstKnownEvent.getEventTimeStamp()) + " older than the request start time " + TimeUtils.convertToISO8601String(start));
// Optimize the rest of the data sources away....
return new ArrayList<DataSourceforPV>(dataSourcesForPV);
}
}
// Add any external servers if any only if the creation time for this type info is after the start time of the request.
Timestamp creationTime = typeInfo.getCreationTime();
if(includeExternalServers(req)) {
if(creationTime == null || start.before(creationTime)) {
List<ChannelArchiverDataServerPVInfo> caServers = this.configService.getChannelArchiverDataServers(pvName);
if(caServers != null) {
for(ChannelArchiverDataServerPVInfo caServer : caServers) {
int count = determineCount(req);
String howStr = determineHowStr(req);
logger.debug("Adding Channel Archiver server for " + pvName + " " + caServer.toString()
+ " and asking for data from " + TimeUtils.convertToHumanReadableString(start)
+ " and " + TimeUtils.convertToHumanReadableString(creationTime));
dataSourcesForPV.add(new DataSourceforPV(pvName, caServer.getServerInfo().getPlugin(count, howStr), lifetimeid++, start, creationTime));
}
}
} else {
logger.debug("Start time "
+ TimeUtils.convertToHumanReadableString(start)
+ " is on or after creation time stamp "
+ TimeUtils.convertToHumanReadableString(creationTime)
+ ". Skipping adding any external data sources...");
}
} else {
logger.debug("Not including external servers on user request for pv" + pvName);
}
return new ArrayList<DataSourceforPV>(dataSourcesForPV);
} catch(Exception ex) {
logger.error("Exception generating data sources for pv " + pvName, ex);
return null;
}
}
private String determineHowStr(HttpServletRequest req) {
String howStr = "0";// By default, we ask for raw data...
try {
String caHowStr = req.getParameter("ca_how");
if(caHowStr != null) {
// We try to parse the how to make sure it is an int.
Integer.parseInt(caHowStr);
}
} catch(Exception ex) {
logger.warn("Exception parsing ca_how", ex);
}
return howStr;
}
private int determineCount(HttpServletRequest req) {
String countStr = req.getParameter("ca_count");
int count = Integer.MAX_VALUE;
if(countStr != null) {
count = Integer.parseInt(countStr);
}
return count;
}
/**
* To prevent infinite loops and such, we can specify that we do not proxy to external servers for this data retrieval request.
* @param req HttpServletRequest
* @return boolean True or False
*/
public static boolean includeExternalServers(HttpServletRequest req) {
String skipExternalServersStr = req.getParameter("skipExternalServers");
if(skipExternalServersStr != null) {
try {
boolean skipExternalServers = Boolean.parseBoolean(skipExternalServersStr);
if(skipExternalServers) {
// We want to skip external servers; so we tell the caller not to include external servers.
return false;
}
} catch(Exception ex) {
logger.error("Exception parsing external servers inclusion str" + skipExternalServersStr, ex);
}
}
return true;
}
}