/* ====================================================================
* Limited Evaluation License:
*
* This software is open source, but licensed. The license with this package
* is an evaluation license, which may not be used for productive systems. If
* you want a full license, please contact us.
*
* The exclusive owner of this work is the OpenRate project.
* This work, including all associated documents and components
* is Copyright of the OpenRate project 2006-2015.
*
* The following restrictions apply unless they are expressly relaxed in a
* contractual agreement between the license holder or one of its officially
* assigned agents and you or your organisation:
*
* 1) This work may not be disclosed, either in full or in part, in any form
* electronic or physical, to any third party. This includes both in the
* form of source code and compiled modules.
* 2) This work contains trade secrets in the form of architecture, algorithms
* methods and technologies. These trade secrets may not be disclosed to
* third parties in any form, either directly or in summary or paraphrased
* form, nor may these trade secrets be used to construct products of a
* similar or competing nature either by you or third parties.
* 3) This work may not be included in full or in part in any application.
* 4) You may not remove or alter any proprietary legends or notices contained
* in or on this work.
* 5) This software may not be reverse-engineered or otherwise decompiled, if
* you received this work in a compiled form.
* 6) This work is licensed, not sold. Possession of this software does not
* imply or grant any right to you.
* 7) You agree to disclose any changes to this work to the copyright holder
* and that the copyright holder may include any such changes at its own
* discretion into the work
* 8) You agree not to derive other works from the trade secrets in this work,
* and that any such derivation may make you liable to pay damages to the
* copyright holder
* 9) You agree to use this software exclusively for evaluation purposes, and
* that you shall not use this software to derive commercial profit or
* support your business or personal activities.
*
* This software is provided "as is" and any expressed or impled warranties,
* including, but not limited to, the impled warranties of merchantability
* and fitness for a particular purpose are disclaimed. In no event shall
* The OpenRate Project or its officially assigned agents be liable to any
* direct, indirect, incidental, special, exemplary, or consequential damages
* (including but not limited to, procurement of substitute goods or services;
* Loss of use, data, or profits; or any business interruption) however caused
* and on theory of liability, whether in contract, strict liability, or tort
* (including negligence or otherwise) arising in any way out of the use of
* this software, even if advised of the possibility of such damage.
* This software contains portions by The Apache Software Foundation, Robert
* Half International.
* ====================================================================
*/
package OpenRate.cache;
import OpenRate.OpenRate;
import OpenRate.configurationmanager.ClientManager;
import OpenRate.configurationmanager.IEventInterface;
import OpenRate.exception.InitializationException;
import OpenRate.exception.ProcessingException;
import OpenRate.logging.LogUtil;
import OpenRate.transaction.ISyncPoint;
import OpenRate.utils.PropertyUtils;
import java.io.*;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
/**
* This is a cache implementing a persistent in-memory hash table, which must be
* saved on shutdown or periodically.
*/
public class PersistentIndexedObject
extends AbstractCache
implements ICacheLoader,
ICacheSaver,
IEventInterface,
ISyncPoint {
/**
* The name of the location we store the information in the persistent
* storage. Can be a file name or a table name.
*/
protected String CachePersistenceName = null;
/**
* This stores all the cacheable data. The DigitTree class is a way of storing
* numeric values for a best match search. The cost of a search is linear with
* the number of digits stored in the search tree
*/
protected HashMap<String, Object> ObjectList;
// List of Services that this Client supports
private final static String SERVICE_PERSIST = "Persist";
private final static String SERVICE_PURGE = "Purge";
private final static String SERVICE_OBJECT_COUNT = "ObjectCount";
private final static String SERVICE_DUMP_OBJECTS = "DumpObjects";
private final static String SERVICE_INITIAL_HASH_SIZE = "InitialHashSize";
private final static String DEFAULT_INITIAL_HASH_SIZE = "50000";
private final static String SERVICE_REMOVE_KEY = "RemoveKey";
// Variables for managing the sync points
private int SyncStatus = 0;
// Variable holding the initial hash size
private int initialHashSize;
// -----------------------------------------------------------------------------
// ------------------ Start of inherited Plug In functions ---------------------
// -----------------------------------------------------------------------------
/**
* loadCache is called automatically on startup of the cache factory, as a
* result of implementing the CacheLoader interface. This should be used to
* load any data that needs loading, and to set up variables.
*
* @param ResourceName The resource name we are loading for
* @param CacheName The cache name we are loading for
* @throws InitializationException
*/
@Override
public void loadCache(String ResourceName, String CacheName)
throws InitializationException {
// Variable declarations
boolean foundDataSourceType = false;
String DataSourceType;
// Get the source of the data to load
setSymbolicName(CacheName);
// Find the location of the zone configuration file
OpenRate.getOpenRateFrameworkLog().info("Starting Persistent Cache Loading <" + getSymbolicName() + ">");
DataSourceType = PropertyUtils.getPropertyUtils().getDataCachePropertyValueDef(ResourceName,
CacheName,
"DataSourceType",
"None");
if (DataSourceType.equalsIgnoreCase("File")) {
foundDataSourceType = true;
}
if (DataSourceType.equalsIgnoreCase("DB")) {
message = "Persistent Cache does not yet support DB persistence sources";
OpenRate.getOpenRateFrameworkLog().error(message);
throw new InitializationException(message, getSymbolicName());
}
if (!foundDataSourceType) {
message = "DataSourceType for cache <" + getSymbolicName()
+ "> must be File, found <" + DataSourceType + ">";
OpenRate.getOpenRateFrameworkLog().error(message);
throw new InitializationException(message, getSymbolicName());
}
// Get the configuration we are working on
if (DataSourceType.equalsIgnoreCase("File")) {
CachePersistenceName = PropertyUtils.getPropertyUtils().getDataCachePropertyValueDef(ResourceName,
CacheName,
"DataFile",
"None");
if (CachePersistenceName.equals("None")) {
message = "Data source file name not found for cache <" + getSymbolicName() + ">";
OpenRate.getOpenRateFrameworkLog().error(message);
throw new InitializationException(message, getSymbolicName());
} else {
OpenRate.getOpenRateFrameworkLog().debug(
"Found Persistence File Configuration <"
+ CachePersistenceName + ">");
}
}
// Get the initial hash size
String tmpInitialSize = PropertyUtils.getPropertyUtils().getDataCachePropertyValueDef(ResourceName,
CacheName,
SERVICE_INITIAL_HASH_SIZE,
DEFAULT_INITIAL_HASH_SIZE);
try {
initialHashSize = Integer.valueOf(tmpInitialSize);
} catch (NumberFormatException ex) {
message = "Expected a numeric value for <" + SERVICE_INITIAL_HASH_SIZE + "> in cache <" + getSymbolicName() + ">, but got <" + tmpInitialSize + ">";
OpenRate.getOpenRateFrameworkLog().error(message);
throw new InitializationException(message, getSymbolicName());
}
// Initialise the object cache
ObjectList = new HashMap<>(initialHashSize);
// perform the actual loading
loadCacheObjectsFromFile();
}
/**
* putObject inserts the given object into the cache. If the object already
* exists, we update it overwriting the previous version
*
* @param RecordKey the hash key to use
* @param ObjectToCache the object to store in the hash
*/
public void putObject(String RecordKey, Object ObjectToCache) {
ObjectList.put(RecordKey, ObjectToCache);
}
/**
* getObject retrieves the given object from the cache, as defined by the key
*
* @param RecordKey the hash key to use
* @return Object the object stored in the hash, otherwise null if none found
*/
public Object getObject(String RecordKey) {
if (ObjectList.containsKey(RecordKey)) {
return ObjectList.get(RecordKey);
} else {
return null;
}
}
/**
* deleteObject removes the given object from the cache, as defined by the key
*
* @param RecordKey the hash key to remove
*/
public void deleteObject(String RecordKey) {
if (ObjectList.containsKey(RecordKey)) {
ObjectList.remove(RecordKey);
}
}
/**
* See if an key exists in the cache
*
* @param recordKey The object key to find
* @return true if the object exists otherwise false
*/
public boolean containsObjectKey(String recordKey) {
return ObjectList.containsKey(recordKey);
}
/**
* Get the key set for the cache, used for iterating over it
*
* @return the object key set
*/
public Set<String> getObjectKeySet() {
return ObjectList.keySet();
}
// -----------------------------------------------------------------------------
// ------------- Start of inherited IEventInterface functions ------------------
// -----------------------------------------------------------------------------
/**
* This is used for the pipeline synchronisation. See the description in the
* OpenRate framework module to understand how this works.
*/
@Override
public int getSyncStatus() {
return SyncStatus;
}
/**
* This is used for the pipeline synchronisation. See the description in the
* OpenRate framework module to understand how this works.
*
* *** This is a stub function for the moment ***
*/
@Override
public void setSyncStatus(int newStatus) {
if (newStatus == 2) {
SyncStatus = 3;
} else if (newStatus == 4) {
SyncStatus = 5;
} else {
SyncStatus = newStatus;
}
}
// -----------------------------------------------------------------------------
// ---------------- Start of inherited ICacheSaver functions -------------------
// -----------------------------------------------------------------------------
/**
* Save the internal object store to the persistence target
*
* @throws OpenRate.exception.ProcessingException
*/
@Override
public void saveCache() throws ProcessingException {
saveCacheObjectsToFile();
}
// -----------------------------------------------------------------------------
// ------------- 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 {
//Register this Client
ClientManager.getClientManager().registerClient("Resource", getSymbolicName(), this);
//Register services for this Client
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_PURGE, ClientManager.PARAM_DYNAMIC);
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_OBJECT_COUNT, ClientManager.PARAM_NONE);
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_INITIAL_HASH_SIZE, ClientManager.PARAM_NONE);
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_DUMP_OBJECTS, ClientManager.PARAM_NONE);
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_REMOVE_KEY, 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) {
int ResultCode = -1;
if (Command.equalsIgnoreCase(SERVICE_PURGE)) {
if (Parameter.equalsIgnoreCase("true")) {
// Clear the persistence object
ObjectList.clear();
ResultCode = 0;
} else if (Parameter.isEmpty()) {
return "false";
} else {
return getSymbolicName() + ":" + SERVICE_PURGE + "=true to purge cache";
}
}
// Return the number of objects in the cache
if (Command.equalsIgnoreCase(SERVICE_OBJECT_COUNT)) {
return Integer.toString(ObjectList.size());
}
// Return the number initial size of the hash
if (Command.equalsIgnoreCase(SERVICE_INITIAL_HASH_SIZE)) {
return Integer.toString(initialHashSize);
}
if (Command.equalsIgnoreCase(SERVICE_DUMP_OBJECTS)) {
if (Parameter.equalsIgnoreCase("true")) {
// Clear the persistence object
dumpObjects();
ResultCode = 0;
} else if (Parameter.isEmpty()) {
return "false";
} else {
return getSymbolicName() + ":" + SERVICE_DUMP_OBJECTS + "=true to dump stored objects";
}
}
if (Command.equalsIgnoreCase(SERVICE_PERSIST)) {
try {
saveCache();
} catch (ProcessingException ex) {
OpenRate.getOpenRateFrameworkLog().error("Command SERVICE_PERSIST not executed because of Exception thrown by saveCache()", ex);
return "Command not executed because of Exception thrown by saveCache()";
}
ResultCode = 0;
}
if (Command.equalsIgnoreCase(SERVICE_REMOVE_KEY)) {
if (!Parameter.isEmpty()) {
if (ObjectList.containsKey(Parameter)) {
ObjectList.remove(Parameter);
} else {
return "cound not find key " + Parameter + "\n";
}
ResultCode = 0;
} else if (Parameter.isEmpty()) {
return "false";
} else {
return getSymbolicName() + ":" + SERVICE_DUMP_OBJECTS + "=true to dump stored objects";
}
}
if (ResultCode == 0) {
OpenRate.getOpenRateFrameworkLog().debug(LogUtil.LogECICacheCommand(getSymbolicName(), Command, Parameter));
return "OK";
} else {
return "Command Not Understood\n";
}
}
/**
* Save the object data to a file. This works with objects that are
* serializable in the case that yours are not, you must overwrite this in an
* inherited class.
*
* @throws OpenRate.exception.ProcessingException
*/
public void saveCacheObjectsToFile() throws ProcessingException {
FileOutputStream outStream = null;
ObjectOutputStream objOutStream;
// Check to see if we need to open our output stream
if (CachePersistenceName != null) {
try {
outStream = new FileOutputStream(CachePersistenceName);
} catch (FileNotFoundException ex) {
message = "File not found saving persistent objects";
OpenRate.getOpenRateFrameworkLog().fatal(message);
}
try {
objOutStream = new ObjectOutputStream(outStream);
if (ObjectList instanceof Serializable) {
objOutStream.writeObject(ObjectList);
objOutStream.flush();
objOutStream.close();
} else {
OpenRate.getOpenRateFrameworkLog().error("Class contains non-serializable data");
}
} catch (IOException ex) {
message = "IO Exception saving persistent objects";
OpenRate.getOpenRateFrameworkLog().fatal(message);
}
}
}
/**
* Load the object data from a file. This works with objects that are
* serializable in the case that yours are not, you must overwrite this in an
* inherited class.
*/
@SuppressWarnings("unchecked")
public void loadCacheObjectsFromFile() {
FileInputStream inStream;
ObjectInputStream objStream;
try {
inStream = new FileInputStream(CachePersistenceName);
} catch (FileNotFoundException ex) {
OpenRate.getOpenRateFrameworkLog().warning(
"Persistent data file <" + CachePersistenceName
+ "> not found.");
return;
}
try {
objStream = new ObjectInputStream(inStream);
} catch (IOException ex) {
OpenRate.getOpenRateFrameworkLog().warning(
"Persistent data file <" + CachePersistenceName
+ "> could not be opened.");
return;
}
try {
ObjectList = (HashMap<String, Object>) objStream.readObject();
} catch (IOException ex) {
OpenRate.getOpenRateFrameworkLog().warning(
"Persistent data file <" + CachePersistenceName
+ "> could not be read.");
} catch (ClassNotFoundException ex) {
message = "Class not found loading persistent objects";
OpenRate.getOpenRateFrameworkLog().fatal(message);
}
}
/**
* Dump the internal objects. The implementation class is responsible for
* formatting the objects.
*/
public void dumpObjects() {
String objectKey;
Object tmpObject;
String filename = "Dump-" + getSymbolicName() + "-All-" + Calendar.getInstance().getTimeInMillis() + ".dump";
OpenRate.getOpenRateFrameworkLog().info("Dumping PersistentIndexedObject data to file <" + filename + ">");
try (BufferedWriter outFile = new BufferedWriter(new FileWriter(filename))) {
outFile.write("# Balance data dump file\n");
Iterator<String> objectIter = ObjectList.keySet().iterator();
while (objectIter.hasNext()) {
objectKey = objectIter.next();
tmpObject = ObjectList.get(objectKey);
String fileLine = formatObject(objectKey, tmpObject);
outFile.write(fileLine + "\n");
}
outFile.flush();
outFile.close();
} catch (IOException ex) {
message = "IO Error dumping data in cache <" + getSymbolicName() + ">";
OpenRate.getOpenRateFrameworkLog().fatal(message, ex);
}
}
/**
* Format the object for outputting to the dump file. Because we cannot know
* in advance how the Object will be stored, we delegate the formatting to the
* implementation class.
*
* @param key the key to format
* @param object The object to format
* @return The formatted object
*/
public String formatObject(String key, Object object) {
OpenRate.getOpenRateFrameworkLog().warning("Using default formatter, override 'formatObject' method of " + getSymbolicName() + " for a better output");
return key + ":" + object.toString();
}
/**
* Save the object data to a table. This works with objects that are
* serializable in the case that yours are not, you must overwrite this in an
* inherited class.
*/
public void saveCacheObjectsToDB() {
String objectKey;
Object tmpObject;
Iterator<String> objectIter = ObjectList.keySet().iterator();
while (objectIter.hasNext()) {
objectKey = objectIter.next();
tmpObject = ObjectList.get(objectKey);
System.out.println(objectKey + ":" + tmpObject.toString());
}
}
}