/* ====================================================================
* 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.db.DBUtil;
import OpenRate.exception.InitializationException;
import OpenRate.lang.DigitTree;
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.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
/**
* Please <a target='new' href='http://www.open-rate.com/wiki/index.php?title=Best_Match_Cache'>click here</a> to go to wiki page.
* <br>
* <p>
* This class implements an example of a best match cached object
* for use in a Zone lookup based on service code and number
*
* Zone information is read from a file during the "LoadCache" processing
* which is triggered by the CacheFactory. The file is defined in the
* properties. The format of the file is:
*
* SERVICE;PREFIX_DIGITS;RESULT
* SERVICE;PREFIX_DIGITS;RESULT
* ...
*
* e.g.TEL;0039;Italy
* TEL;0049;Europe1
* TEL;0044;Europe1
* ...
*
* This module is intended to be used with the AbstractBestMatch process module,
* which provides the control for reloading.
*
* @author i.sparkes
*/
public class BestMatchCache
extends AbstractSyncLoaderCache
{
/**
* 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, DigitTree> groupCache;
// List of Services that this Client supports
private final static String SERVICE_OBJECT_COUNT = "ObjectCount";
private final static String SERVICE_GROUP_COUNT = "GroupCount";
private final static String SERVICE_DUMP_MAP = "DumpMap";
// This is the null result
private final ArrayList<String> noResult = new ArrayList<>();
/** Constructor
* Creates a new instance of the Group Cache. The group Cache
* contains all of the Best Match Maps that are later cached. The lookup
* is therefore performed within the group, retrieving the best
* match for that group.
*/
public BestMatchCache()
{
super();
// Initialise the group cache
groupCache = new HashMap<>(50);
// create the null result
noResult.add(DigitTree.NO_DIGIT_TREE_MATCH);
}
// -----------------------------------------------------------------------------
// ------------------ Start of inherited Plug In functions ---------------------
// -----------------------------------------------------------------------------
/**
* Load the data from the defined file
* @throws InitializationException
*/
@Override
public void loadDataFromFile() throws InitializationException
{
// Variable declarations
int ZonesLoaded = 0;
BufferedReader inFile;
String tmpFileRecord;
String[] ZoneFields;
ArrayList<String> ChildData = null;
int childDataCounter;
int formFactor = 0;
// Log that we are starting the loading
OpenRate.getOpenRateFrameworkLog().info("Starting Best Match Cache Loading from File for cache <" + getSymbolicName() + ">");
// Try to open the file
try
{
inFile = new BufferedReader(new FileReader(cacheDataFile));
}
catch (FileNotFoundException exFileNotFound)
{
message = "Application is not able to read file : <" +
cacheDataFile + ">";
throw new InitializationException(message,exFileNotFound,getSymbolicName());
}
// File open, now get the stuff
try
{
while (inFile.ready())
{
tmpFileRecord = inFile.readLine();
if ((tmpFileRecord.startsWith("#")) |
tmpFileRecord.trim().equals(""))
{
// Comment line, ignore
}
else
{
ZonesLoaded++;
ZoneFields = tmpFileRecord.split(";");
if (ZoneFields.length != formFactor)
{
// see if we are setting or changing
if (formFactor != 0)
{
// this is a change - NO NO
message = "Form factor change <" + cacheDataFile +
"> in record <" + ZonesLoaded + ">. Originally got <" + formFactor +
"> fields in a record, not getting <" + ZoneFields.length + ">";
throw new InitializationException(message,getSymbolicName());
}
else
{
// setting
formFactor = ZoneFields.length;
}
}
if (ZoneFields.length < 3)
{
// There are not enough fields
message = "Error reading input file <" + cacheDataFile +
"> in record <" + ZonesLoaded + ">. Malformed Record.";
throw new InitializationException(message,getSymbolicName());
}
if (ZoneFields.length >= 3)
{
// we have some child data
ChildData = new ArrayList<>();
for (childDataCounter = 2 ; childDataCounter < ZoneFields.length ; childDataCounter++)
{
ChildData.add(ZoneFields[childDataCounter]);
}
}
addEntry(ZoneFields[0], ZoneFields[1], ChildData);
// Update to the log file
if ((ZonesLoaded % loadingLogNotificationStep) == 0)
{
message = "Best Match Data Loading: <" + ZonesLoaded +
"> configurations loaded for <" + getSymbolicName() + "> from <" +
cacheDataFile + ">";
OpenRate.getOpenRateFrameworkLog().info(message);
}
}
}
}
catch (IOException ex)
{
OpenRate.getOpenRateFrameworkLog().fatal(
"Error reading input file <" + cacheDataSourceName +
"> in record <" + ZonesLoaded + ">. IO Error.");
}
finally
{
try
{
inFile.close();
}
catch (IOException ex)
{
message = "Error closing input file <" + cacheDataSourceName + ">";
throw new InitializationException(message,ex,getSymbolicName());
}
}
OpenRate.getOpenRateFrameworkLog().info(
"Best Match Data Loading completed. <" + ZonesLoaded +
"> configuration lines loaded for <" + getSymbolicName() + " > from <"
+ cacheDataFile + ">");
OpenRate.getOpenRateFrameworkLog().info(
"Loaded <3> base fields and <" + (formFactor - 3) +
"> additional data fields");
}
/**
* Load the data from the defined Data Source
* @throws InitializationException
*/
@Override
public void loadDataFromDB()
throws InitializationException
{
int ZonesLoaded = 0;
String Group;
String DestinationPrefix;
ArrayList<String> ChildData = null;
int childDataCounter;
int formFactor = 0;
// Log that we are starting the loading
OpenRate.getOpenRateFrameworkLog().info("Starting Best Match Cache Loading from DB for <" + getSymbolicName() + ">");
// Try to open the DS
JDBCcon = DBUtil.getConnection(cacheDataSourceName);
// Now prepare the statements
prepareStatements();
// Execute the query
try
{
mrs = StmtCacheDataSelectQuery.executeQuery();
}
catch (SQLException Sex)
{
message = "Error performing SQL for retieving Best Match data. message <" + Sex.getMessage() + ">";
OpenRate.getOpenRateFrameworkLog().fatal(message);
throw new InitializationException(message,getSymbolicName());
}
// loop through the results for the customer login cache
try
{
mrs.beforeFirst();
formFactor = mrs.getMetaData().getColumnCount();
while (mrs.next())
{
ZonesLoaded++;
Group = mrs.getString(1);
DestinationPrefix = mrs.getString(2);
if (formFactor < 3)
{
// There are not enough fields
message = "Error reading input data from <" + cacheDataSourceName +
"> in record <" + ZonesLoaded + ">. Not enough fields.";
throw new InitializationException(message,getSymbolicName());
}
if (formFactor >= 3)
{
// we have some child data
ChildData = new ArrayList<>();
for (childDataCounter = 3 ; childDataCounter <= formFactor ; childDataCounter++)
{
ChildData.add(mrs.getString(childDataCounter));
}
}
// Add the map
addEntry(Group, DestinationPrefix, ChildData);
// Update to the log file
if ((ZonesLoaded % loadingLogNotificationStep) == 0)
{
message = "Best Match Data Loading: <" + ZonesLoaded +
"> configurations loaded for <" + getSymbolicName() + "> from <" +
cacheDataSourceName + ">";
OpenRate.getOpenRateFrameworkLog().info(message);
}
}
}
catch (SQLException ex)
{
message = "Error opening Search Map Data for <" + cacheDataSourceName + ">";
throw new InitializationException(message,ex,getSymbolicName());
}
// Close down stuff
try
{
mrs.close();
StmtCacheDataSelectQuery.close();
JDBCcon.close();
}
catch (SQLException ex)
{
message = "Error closing Search Map Data connection for <" +
cacheDataSourceName + ">";
throw new InitializationException(message,ex,getSymbolicName());
}
OpenRate.getOpenRateFrameworkLog().info(
"Best Match Data Loading completed. <" + ZonesLoaded +
"> configuration lines loaded for <" + getSymbolicName() + "> from <" +
cacheDataSourceName + ">");
OpenRate.getOpenRateFrameworkLog().info("Loaded <3> base fields and <" + (formFactor - 3) +
"> additional data fields");
}
/**
* Load the data from the defined Data Source
* @throws InitializationException
*/
@Override
public void loadDataFromMethod() throws InitializationException
{
int ZonesLoaded = 0;
String Group;
String DestinationPrefix;
ArrayList<String> ChildData = null;
int childDataCounter;
int formFactor = 0;
ArrayList<String> tmpMethodResult;
// Log that we are starting the loading
OpenRate.getOpenRateFrameworkLog().info("Starting Best Match 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())
{
tmpMethodResult = methodDataToLoadIterator.next();
formFactor = tmpMethodResult.size();
if (formFactor < 3)
{
// There are not enough fields
message = "Error reading input data from <" + cacheDataSourceName +
"> in record <" + ZonesLoaded + ">. Not enough fields.";
throw new InitializationException(message,getSymbolicName());
}
ZonesLoaded++;
Group = tmpMethodResult.get(0);
DestinationPrefix = tmpMethodResult.get(1);
if (formFactor >= 3)
{
// we have some child data
ChildData = new ArrayList<>();
for (childDataCounter = 2 ; childDataCounter < formFactor ; childDataCounter++)
{
ChildData.add(tmpMethodResult.get(childDataCounter));
}
}
// Add the map
addEntry(Group, DestinationPrefix, ChildData);
// Update to the log file
if ((ZonesLoaded % loadingLogNotificationStep) == 0)
{
message = "Best Match Data Loading: <" + ZonesLoaded +
"> configurations loaded for <" + getSymbolicName() + "> from <" +
cacheDataSourceName + ">";
OpenRate.getOpenRateFrameworkLog().info(message);
}
}
OpenRate.getOpenRateFrameworkLog().info(
"Best Match Cache Data Loading completed. " + ZonesLoaded +
" configuration lines loaded from <" + cacheDataSourceName +
">");
OpenRate.getOpenRateFrameworkLog().info(
"Loaded <3> base fields and <" + (formFactor - 3) +
"> additional data fields");
}
// -----------------------------------------------------------------------------
// -------------------- Start of custom Plug In functions ----------------------
// -----------------------------------------------------------------------------
/**
* Add a value into the BestMatchCache, defining the result
* value that should be returned in the case of a (best) match.
* The Digit Trees are divided by service
*
* @param mapGroup The group for the zone
* @param prefix The prefix
* @param resultList The result array
* @throws InitializationException
*/
public void addEntry(String mapGroup, String prefix, ArrayList<String> resultList)
throws InitializationException
{
// See if we already have the digit tree for this service
if (!groupCache.containsKey(mapGroup))
{
// Create the new Digit Tree
DigitTree prefixCache = new DigitTree();
groupCache.put(mapGroup, prefixCache);
try
{
prefixCache.addPrefix(prefix, resultList);
}
catch (ArrayIndexOutOfBoundsException aiex)
{
message = "Error Adding Prefix <" + prefix + "> to group <" + mapGroup + "> in module <" + getSymbolicName() + ">";
throw new InitializationException(message,getSymbolicName());
}
}
else
{
// Otherwise just add it to the existing Digit Tree
DigitTree prefixCache = groupCache.get(mapGroup);
try
{
prefixCache.addPrefix(prefix, resultList);
}
catch (ArrayIndexOutOfBoundsException ex)
{
message = "Error Adding Prefix <" + prefix + "> to model <" + mapGroup + "> in module <" + getSymbolicName() + ">";
OpenRate.getOpenRateFrameworkLog().fatal(message);
throw new InitializationException(message,getSymbolicName());
}
}
}
/**
* Get a value from the BestMatchCache.
* If we do not know the service, the result is automatically "no match".
* The Digit Trees are divided by group
*
* @param mapGroup The group
* @param prefix The prefix
* @return The result
*/
public String getMatch(String mapGroup, String prefix)
{
String Value;
// Get the service if we know it
DigitTree prefixCache = groupCache.get(mapGroup);
if (prefixCache != null)
{
Value = prefixCache.match(prefix);
}
else
{
// We don't know the service, so we cannot know the prefix
Value = DigitTree.NO_DIGIT_TREE_MATCH;
}
return Value;
}
/**
* Get a value from the BestMatchCache.
* If we do not know the service, the result is automatically "no match".
* The Digit Trees are divided by group
*
* @param mapGroup The group
* @param prefix The prefix
* @return The result
*/
public ArrayList<String> getMatchWithChildData(String mapGroup, String prefix)
{
// Get the service if we know it
DigitTree prefixCache = groupCache.get(mapGroup);
if (prefixCache != null)
{
return prefixCache.matchWithChildData(prefix);
}
else
{
// We don't know the service, so we cannot know the prefix
return noResult;
}
}
/**
* Clear down the cache contents in the case that we are ordered to reload
*/
@Override
public void clearCacheObjects()
{
groupCache.clear();
}
/**
* Dumps the entire contents of the cache to the Log.
*/
protected void DumpMapData()
{
String Helper;
Iterator<String> GroupIter;
OpenRate.getOpenRateFrameworkLog().info("Dumping Map Data for BestMatchCache <" + getSymbolicName() + ">");
OpenRate.getOpenRateFrameworkLog().info("Groups:");
// Iterate thorough the entries in the group
GroupIter = groupCache.keySet().iterator();
while (GroupIter.hasNext())
{
Helper = GroupIter.next();
OpenRate.getOpenRateFrameworkLog().info(" " + Helper);
}
// Now dump the data
GroupIter = groupCache.keySet().iterator();
while (GroupIter.hasNext())
{
Helper = GroupIter.next();
OpenRate.getOpenRateFrameworkLog().info("Dumping group map data for <" + Helper + ">");
// The rest of the data is horrible to extract - a problem for a rainy day
}
}
// -----------------------------------------------------------------------------
// ------------- 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.
*
*/
@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);
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_DUMP_MAP, 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)
{
DigitTree tmpPrefixCache;
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();
tmpPrefixCache = groupCache.get(tmpGroupName);
Objects += tmpPrefixCache.size();
}
return Integer.toString(Objects);
}
// Return the number of objects in the cache
if (Command.equalsIgnoreCase(SERVICE_DUMP_MAP))
{
// onl< dump on a positive command
if (Parameter.equalsIgnoreCase("true"))
{
DumpMapData();
}
ResultCode = 0;
}
if (ResultCode == 0)
{
OpenRate.getOpenRateFrameworkLog().debug(LogUtil.LogECICacheCommand(getSymbolicName(), Command, Parameter));
return "OK";
}
else
{
// pass the event up the stack
return super.processControlEvent(Command,Init,Parameter);
}
}
}