package OpenRate.cache;
import OpenRate.CommonConfig;
import OpenRate.OpenRate;
import OpenRate.configurationmanager.ClientManager;
import OpenRate.db.DBUtil;
import OpenRate.exception.InitializationException;
import OpenRate.logging.LogUtil;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
/**
* This class implements a common function of find the mapping of a given
* resource, over time to a key.
*/
public class ValidityFromCache
extends AbstractSyncLoaderCache {
// This stores the index to all the groups.
private final HashMap<String, HashMap<String, ValidityNode>> GroupCache;
/**
* A ValidityNode is a segment of validity of a resource. These are chained
* together in a sorted linked list. The sorting is done at insertion time
* into the list, meaning that lookups at run time can be optimised.
*/
private class ValidityNode {
long TimeFrom;
ArrayList<String> Results = null;
ValidityNode child = null;
}
// List of Services that this Client supports
private final static String SERVICE_GROUP_COUNT = "GroupCount";
private final static String SERVICE_OBJECT_COUNT = "ObjectCount";
/**
* The default return when there is no match
*/
public static final String NO_VALIDITY_MATCH = "NOMATCH";
/**
* Creates a new instance of the group Cache. The group Cache contains all of
* the Groups that are later cached. The lookup is therefore performed within
* the group, retrieving the validity segment for that group, resource_id and
* time.
*/
public ValidityFromCache() {
super();
GroupCache = new HashMap<>(500000);
}
/**
* Add a value into the Validity Segment Cache, defining the result value that
* should be returned in the case of a match. The entries are ordered during
* the loading in a linked list sorted by validity date. This makes the search
* at run time easier.
*
* @param Group The data group to add the entry to
* @param ResourceID The resourceID of the entry to add
* @param StartTime The start time of the validity
* @param Results The list of result values
*/
public void addEntry(String Group, String ResourceID, long StartTime,
ArrayList<String> Results) {
HashMap<String, ValidityNode> tmpResourceCache;
ValidityNode tmpValidityNode;
ValidityNode newNode;
// See if we already have the group cache for this Group
if (!GroupCache.containsKey(Group)) {
// Create the new resource cache
tmpResourceCache = new HashMap<>(100);
// Add it to the group cache
GroupCache.put(Group, tmpResourceCache);
} else {
// Otherwise just get the existing object
tmpResourceCache = GroupCache.get(Group);
}
// Now add the validity segment into the list
if (!tmpResourceCache.containsKey(ResourceID)) {
// Create the new list
tmpValidityNode = new ValidityNode();
tmpValidityNode.TimeFrom = StartTime;
tmpValidityNode.Results = Results;
tmpValidityNode.child = null;
// Add in the new node
tmpResourceCache.put(ResourceID, tmpValidityNode);
} else {
// Recover the validity map that there is
tmpValidityNode = tmpResourceCache.get(ResourceID);
// now run down the validity periods until we find the right position
while (tmpValidityNode != null) {
if (StartTime < tmpValidityNode.TimeFrom) {
// insert before the current node
newNode = new ValidityNode();
newNode.child = tmpValidityNode;
newNode.TimeFrom = StartTime;
newNode.Results = Results;
// Update the head pointer in the hashmap
tmpResourceCache.put(ResourceID, newNode);
// done
return;
} else if ((StartTime > tmpValidityNode.TimeFrom)
& (tmpValidityNode.child == null)) {
// insert at the tail of the list
newNode = new ValidityNode();
tmpValidityNode.child = newNode;
newNode.TimeFrom = StartTime;
newNode.Results = Results;
// done
return;
}
// Move down the map
tmpValidityNode = tmpValidityNode.child;
}
// If we get here, we could not insert the period
OpenRate.getOpenRateFrameworkLog().error("Cache <" + getSymbolicName()
+ "> could not insert <" + Group + ":" + ResourceID + ":"
+ StartTime + ":" + "> without overlap.");
}
}
/**
* Returns the entry matching the resourceID in the given group at the given
* time
*
* @param Group The resource group to search in
* @param ResourceID The resource identifier to search for
* @param Time The time to search for
* @return The retrieved value, or "NOMATCH" if none found
*/
public String getValiditySegmentMatch(String Group, String ResourceID, long Time) {
HashMap<String, ValidityNode> tmpResourceCache;
ValidityNode tmpValidityNode;
// Get the service if we know it
tmpResourceCache = GroupCache.get(Group);
if (tmpResourceCache != null) {
tmpValidityNode = tmpResourceCache.get(ResourceID);
// Now that we have the Validity Map, get the entry
while (tmpValidityNode != null) {
if (Time >= tmpValidityNode.TimeFrom) {
if (tmpValidityNode.child == null) {
// end of chain, return what we have
return tmpValidityNode.Results.get(0);
} else {
// next in chain is not valid for out time
if (tmpValidityNode.child.TimeFrom > Time) {
return tmpValidityNode.Results.get(0);
}
}
}
// Move down the map
tmpValidityNode = tmpValidityNode.child;
}
}
return NO_VALIDITY_MATCH;
}
/**
* Returns the vector matching the resourceID in the given group at the given
* time
*
* @param Group The resource group to search in
* @param ResourceID The resource identifier to search for
* @param Time The time to search for
* @return The retrieved value vector, or null if none found
*/
public ArrayList<String> getValiditySegmentMatchWithChildData(String Group, String ResourceID, long Time) {
HashMap<String, ValidityNode> tmpResourceCache;
ValidityNode tmpValidityNode;
ArrayList<String> Value = null;
// Get the service if we know it
tmpResourceCache = GroupCache.get(Group);
if (tmpResourceCache != null) {
tmpValidityNode = tmpResourceCache.get(ResourceID);
// Now that we have the Validity Map, get the entry
while (tmpValidityNode != null) {
if (Time >= tmpValidityNode.TimeFrom) {
if (tmpValidityNode.child == null) {
// end of chain, return what we have
return tmpValidityNode.Results;
} else {
// next in chain is not valid for out time
if (tmpValidityNode.child.TimeFrom > Time) {
return tmpValidityNode.Results;
}
}
}
// Move down the map
tmpValidityNode = tmpValidityNode.child;
}
}
return Value;
}
// -----------------------------------------------------------------------------
// ------------------ Start of inherited Plug In functions ---------------------
// -----------------------------------------------------------------------------
/**
* Load the data from the defined file
*
* @throws OpenRate.exception.InitializationException
*/
@Override
public void loadDataFromFile()
throws InitializationException {
// Variable declarations
int ValidityPeriodsLoaded = 0;
BufferedReader inFile;
String tmpFileRecord;
String[] ZoneFields;
String Group;
String ResourceID;
long TimeFrom;
ArrayList<String> Result;
int Index;
String tmpStartDate = null;
// Find the location of the zone configuration file
OpenRate.getOpenRateFrameworkLog().info("Starting Validity Segment Cache Loading from File");
// Try to open the file
try {
inFile = new BufferedReader(new FileReader(cacheDataFile));
} catch (FileNotFoundException ex) {
message = "Application is not able to read file : <"
+ cacheDataSourceName + ">";
OpenRate.getOpenRateFrameworkLog().error(message);
throw new InitializationException(message, ex, getSymbolicName());
}
// File open, now get the stuff
try {
while (inFile.ready()) {
tmpFileRecord = inFile.readLine();
if ((tmpFileRecord.startsWith("#"))
| tmpFileRecord.trim().equals("")) {
// Comment line or whitespace line, ignore
} else {
ValidityPeriodsLoaded++;
ZoneFields = tmpFileRecord.split(";");
Group = ZoneFields[0];
ResourceID = ZoneFields[1];
tmpStartDate = ZoneFields[2];
TimeFrom = (int) fieldInterpreter.convertInputDateToUTC(tmpStartDate);
// Interpret 0 values
if (TimeFrom == 0) {
TimeFrom = CommonConfig.LOW_DATE;
}
// now make an ArrayList of the results
Result = new ArrayList<>();
for (Index = 4; Index < ZoneFields.length; Index++) {
Result.add(ZoneFields[Index]);
}
// Interpret 0 values
if (TimeFrom == 0) {
TimeFrom = CommonConfig.LOW_DATE;
}
addEntry(Group, ResourceID, TimeFrom, Result);
// Update to the log file
if ((ValidityPeriodsLoaded % loadingLogNotificationStep) == 0) {
message = "Validity Segment Map Data Loading: <" + ValidityPeriodsLoaded
+ "> configurations loaded for <" + getSymbolicName() + "> from <"
+ cacheDataFile + ">";
OpenRate.getOpenRateFrameworkLog().info(message);
}
}
}
} catch (IOException ex) {
OpenRate.getOpenRateFrameworkLog().fatal(
"Error reading input file <" + cacheDataFile
+ "> in record <" + ValidityPeriodsLoaded + ">. IO Error.");
} catch (ArrayIndexOutOfBoundsException ex) {
OpenRate.getOpenRateFrameworkLog().fatal(
"Error reading input file <" + cacheDataFile
+ "> in record <" + ValidityPeriodsLoaded + ">. Malformed Record.");
} catch (ParseException pe) {
message
= "Error converting date from <" + getSymbolicName() + "> in record <"
+ ValidityPeriodsLoaded + ">. Unexpected date value <" + tmpStartDate
+ ">";
OpenRate.getOpenRateFrameworkLog().fatal(message);
throw new InitializationException(message, getSymbolicName());
} finally {
try {
inFile.close();
} catch (IOException ex) {
OpenRate.getOpenRateFrameworkLog().error(
"Error closing input file <" + cacheDataFile
+ ">", ex);
}
}
OpenRate.getOpenRateFrameworkLog().info(
"Validity Segment Map Data Loading completed. "
+ ValidityPeriodsLoaded + " configuration lines loaded from <"
+ cacheDataFile + ">");
}
/**
* Load the data from the defined Data Source
*
* @throws OpenRate.exception.InitializationException
*/
@Override
public void loadDataFromDB()
throws InitializationException {
int ValidityPeriodsLoaded = 0;
String Group;
String ResourceID;
long TimeFrom;
ArrayList<String> Result;
int Index;
String tmpStartDate = null;
// Find the location of the zone configuration file
OpenRate.getOpenRateFrameworkLog().info("Starting Validity Segment Cache Loading from DB in <" + getSymbolicName() + ">");
// Try to open the DS
JDBCcon = DBUtil.getConnection(cacheDataSourceName);
// Now prepare the statements
prepareStatements();
// Execute the query
try {
mrs = StmtCacheDataSelectQuery.executeQuery();
} catch (SQLException ex) {
message = "Error performing SQL for retrieving Validity Segment Match data in <" + getSymbolicName() + ">. message = <" + ex.getMessage() + ">";
OpenRate.getOpenRateFrameworkLog().fatal(message);
throw new InitializationException(message, getSymbolicName());
}
// loop through the results for the customer login cache
try {
mrs.beforeFirst();
while (mrs.next()) {
ValidityPeriodsLoaded++;
Group = mrs.getString(1);
ResourceID = mrs.getString(2);
tmpStartDate = mrs.getString(3);
TimeFrom = (int) fieldInterpreter.convertInputDateToUTC(tmpStartDate);
// Interpret 0 values
if (TimeFrom == 0) {
TimeFrom = CommonConfig.LOW_DATE;
}
// now make an ArrayList of the results
Result = new ArrayList<>();
for (Index = 4; Index <= mrs.getMetaData().getColumnCount(); Index++) {
Result.add(mrs.getString(Index));
}
// Add the map
addEntry(Group, ResourceID, TimeFrom, Result);
// Update to the log file
if ((ValidityPeriodsLoaded % loadingLogNotificationStep) == 0) {
message = "Validity Segment Map Data Loading: <" + ValidityPeriodsLoaded
+ "> configurations loaded for <" + getSymbolicName() + "> from <"
+ cacheDataSourceName + ">";
OpenRate.getOpenRateFrameworkLog().info(message);
}
}
} catch (SQLException ex) {
message = "Error opening Validity Segment Match Data for <" + getSymbolicName() + ">. message = <" + ex.getMessage() + ">";
OpenRate.getOpenRateFrameworkLog().fatal(message);
throw new InitializationException(message, getSymbolicName());
} catch (ParseException pe) {
message
= "Error converting date from <" + getSymbolicName() + "> in record <"
+ ValidityPeriodsLoaded + ">. Unexpected date value <" + tmpStartDate
+ ">. message = <" + pe.getMessage() + ">";
OpenRate.getOpenRateFrameworkLog().fatal(message, pe);
throw new InitializationException(message, getSymbolicName());
}
// Close down stuff
try {
mrs.close();
StmtCacheDataSelectQuery.close();
JDBCcon.close();
} catch (SQLException ex) {
message = "Error closing Validity Segment Match Data for <" + getSymbolicName() + ">. message = <" + ex.getMessage() + ">";
OpenRate.getOpenRateFrameworkLog().fatal(message);
throw new InitializationException(message, getSymbolicName());
}
OpenRate.getOpenRateFrameworkLog().info(
"Validity Segment Map Data Loading completed. "
+ ValidityPeriodsLoaded + " configuration lines loaded from <"
+ cacheDataSourceName + ">");
}
/**
* Load the data from the defined Data Source Method
*
* @throws OpenRate.exception.InitializationException
*/
@Override
public void loadDataFromMethod()
throws InitializationException {
// Variable declarations
int ValidityPeriodsLoaded = 0;
int formFactor;
String Group;
String ResourceID;
long TimeFrom;
ArrayList<String> Result;
int Index;
ArrayList<String> tmpMethodResult;
// Find the location of the zone configuration file
OpenRate.getOpenRateFrameworkLog().info("Starting Validity Segment Cache Loading from Method for <" + getSymbolicName() + ">");
// Execute the user domain method
Collection<ArrayList<String>> methodLoadResultSet;
methodLoadResultSet = getMethodData(getSymbolicName(), CacheMethodName);
Iterator<ArrayList<String>> methodDataToLoadIterator = methodLoadResultSet.iterator();
// loop through the results for the customer login cache
while (methodDataToLoadIterator.hasNext()) {
ValidityPeriodsLoaded++;
tmpMethodResult = methodDataToLoadIterator.next();
formFactor = tmpMethodResult.size();
if (formFactor < 5) {
// There are not enough fields
message = "Error reading input data from <" + cacheDataSourceName
+ "> in record <" + ValidityPeriodsLoaded + ">. Not enough fields.";
OpenRate.getOpenRateFrameworkLog().fatal(message);
throw new InitializationException(message, getSymbolicName());
}
Group = tmpMethodResult.get(0);
ResourceID = tmpMethodResult.get(1);
TimeFrom = Integer.valueOf(tmpMethodResult.get(2));
// Interpret 0 values
if (TimeFrom == 0) {
TimeFrom = CommonConfig.LOW_DATE;
}
// now make an ArrayList of the results
Result = new ArrayList<>();
for (Index = 4; Index < tmpMethodResult.size(); Index++) {
Result.add(tmpMethodResult.get(Index));
}
addEntry(Group, ResourceID, TimeFrom, Result);
// Update to the log file
if ((ValidityPeriodsLoaded % loadingLogNotificationStep) == 0) {
message = "Validity Segment Map Data Loading: <" + ValidityPeriodsLoaded
+ "> configurations loaded for <" + getSymbolicName() + "> from <"
+ cacheDataSourceName + ">";
OpenRate.getOpenRateFrameworkLog().info(message);
}
}
OpenRate.getOpenRateFrameworkLog().info(
"Validity Segment Map Data Loading completed. "
+ ValidityPeriodsLoaded + " configuration lines loaded from <"
+ cacheDataSourceName + ">");
}
// -----------------------------------------------------------------------------
// ------------- Start of inherited IEventInterface functions ------------------
// -----------------------------------------------------------------------------
/**
* registerClientManager registers the client module to the ClientManager
* class which manages all the client modules available in this OpenRate
* Application.
*
* registerClientManager registers this class as a client of the ECI listener
* and publishes the commands that the plug in understands. The listener is
* responsible for delivering only these commands to the plug in.
*
* @throws OpenRate.exception.InitializationException
*/
@Override
public void registerClientManager() throws InitializationException {
// Set the client reference and the base services first
super.registerClientManager();
//Register services for this Client
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_GROUP_COUNT, ClientManager.PARAM_DYNAMIC);
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_OBJECT_COUNT, ClientManager.PARAM_DYNAMIC);
}
/**
* processControlEvent is the method that will be called when an event is
* received for a module that has registered itself as a client of the
* External Control Interface
*
* @param Command - command that is understand by the client module
* @param Init - we are performing initial configuration if true
* @param Parameter - parameter for the command
* @return The result string of the operation
*/
@Override
public String processControlEvent(String Command, boolean Init,
String Parameter) {
HashMap<String, ValidityNode> tmpResource;
Collection<String> tmpGroups;
Iterator<String> GroupIter;
String tmpGroupName;
int Objects = 0;
int ResultCode = -1;
// Return the number of objects in the cache
if (Command.equalsIgnoreCase(SERVICE_GROUP_COUNT)) {
return Integer.toString(GroupCache.size());
}
if (Command.equalsIgnoreCase(SERVICE_OBJECT_COUNT)) {
tmpGroups = GroupCache.keySet();
GroupIter = tmpGroups.iterator();
while (GroupIter.hasNext()) {
tmpGroupName = GroupIter.next();
tmpResource = GroupCache.get(tmpGroupName);
Objects += tmpResource.size();
}
return Integer.toString(Objects);
}
if (ResultCode == 0) {
OpenRate.getOpenRateFrameworkLog().debug(LogUtil.LogECICacheCommand(getSymbolicName(), Command, Parameter));
return "OK";
} else {
return super.processControlEvent(Command, Init, Parameter);
}
}
/**
* Clear down the cache contents in the case that we are ordered to reload
*/
@Override
public void clearCacheObjects() {
GroupCache.clear();
}
}