/* ==================================================================== * 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.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.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; /** * This class implements an in-memory lookup, looking a number up in a range, * and returning the appropriate result. This is useful for looking up if a * number belongs to a range of numbers. * * This example will load the map data from a file or a DB, and expects at * least 6 fields: * - Group - grouping key for subdividing information * - Range from - start of the number range * - Range to - end of the number range * - Time from - start of the validity period * - Time to - end of the validity period * - Result field 1 - result (mandatory) * [- Result field 2] - more results (optional) * [- Result field n] - more results (optional) * * "Range From" must always be less than or equal to "Range To". Overlapping * ranges in a group are not allowed. * * Loading from a file: * -------------------- * Define "DataSourecType" as "File" * Define "DataFile" to point to the (relative or absolute) location of the * file to load * * Loading from a DB: * ------------------ * Define "DataSourecType" as "DB" * Define "DataSource" to point to the data source name to load from * Define "SelectStatement" to return the data you wish to retrieve * */ public class NumberRangeCache extends AbstractSyncLoaderCache { // This is the management structure which allows us to order the ranges private class RangeItem { long RangeFrom; long RangeTo; long ValidityFrom; long ValidityTo; RangeItem nextRange; ArrayList<String> Results; } /** * This stores the index to all the groups. Groups are used to subdivide the * entries in the cache in order that we do not need to search through all * the global possibilities to find ours. We only have to search through the * group of similar entries. */ private HashMap<String, RangeItem> GroupCache; // List of Services that this Client supports private final static String SERVICE_OBJECT_COUNT = "ObjectCount"; private final static String SERVICE_GROUP_COUNT = "GroupCount"; /** * The default return when there is no match */ public static final String NO_RANGE_MATCH = "NOMATCH"; /** * Creates a new instance of the Indexed Match Cache. The Cache contains all * of the Objects that are later cached. The lookup is performed using the * indexes that created at loading time. */ public NumberRangeCache() { super(); GroupCache = new HashMap<>(500); } // ----------------------------------------------------------------------------- // ------------------ Start of inherited Plug In functions --------------------- // ----------------------------------------------------------------------------- /** * Add an object into the Object Cache, creating a chain of values to search, * ordered by "RangeFrom". * @param Group The group to add the entry to * @param ValidityFrom The start of the validity of the range * @param ValidityTo The end of the validity of the range * @param RangeFrom The start of the range * @param RangeTo The end of the range * @param Results The results associated with this range * @throws InitializationException */ public void addEntry(String Group, long RangeFrom, long RangeTo, long ValidityFrom, long ValidityTo, ArrayList<String> Results) throws InitializationException { RangeItem tmpRangeItem; RangeItem newRangeItem; RangeItem tmpRangeNextNode; // these hold the modified values long tmpRF = RangeFrom; long tmpRT = RangeTo; long tmpVF = ValidityFrom; long tmpVT = ValidityTo; // check that the range is OK if (RangeFrom > RangeTo) { message = "Range From <" + RangeFrom + "> cannot be larger than Range To <" + RangeTo + "> in group <" + Group + ">"; throw new InitializationException(message,getSymbolicName()); } if ((ValidityFrom > ValidityTo) && (ValidityTo > 0)) { message = "Validity From <" + ValidityFrom + "> cannot be larger than Validity To <" + ValidityTo + "> in group <" + Group + ">"; throw new InitializationException(message,getSymbolicName()); } // make sure that we deal with the "don't care" cases if (RangeFrom == 0) { tmpRF = Long.MIN_VALUE; } if (RangeTo == 0) { tmpRT = Long.MAX_VALUE; } if (ValidityFrom == 0) { tmpVF = CommonConfig.LOW_DATE; } if (ValidityTo == 0) { tmpVT = CommonConfig.HIGH_DATE; } // Get/Create the group cache if (GroupCache.containsKey(Group)) { tmpRangeItem = GroupCache.get(Group); // now run down the ranges until we find the right position while (tmpRangeItem != null) { tmpRangeNextNode = tmpRangeItem.nextRange; if ((tmpRF > tmpRangeItem.RangeFrom) & (tmpRangeNextNode == null)) { // insert at the tail of the list if we are able newRangeItem = new RangeItem(); newRangeItem.RangeFrom = tmpRF; newRangeItem.RangeTo = tmpRT; newRangeItem.ValidityFrom = tmpVF; newRangeItem.ValidityTo = tmpVT; newRangeItem.Results = Results; // Link to the previous range tmpRangeItem.nextRange = newRangeItem; // done return; } else if (tmpRF < tmpRangeItem.RangeFrom) { // insert at the head/middle of the list newRangeItem = new RangeItem(); newRangeItem.RangeFrom = tmpRF; newRangeItem.RangeTo = tmpRT; newRangeItem.ValidityFrom = tmpVF; newRangeItem.ValidityTo = tmpVT; newRangeItem.Results = Results; newRangeItem.nextRange = tmpRangeItem.nextRange; tmpRangeItem.nextRange = newRangeItem; // done return; } // Move down the map tmpRangeItem = tmpRangeItem.nextRange; } // If we get here, we could not insert the period message = "Range From <" + RangeFrom + "> to <" + RangeTo + "> overlaps with another range in group <" + Group + ">"; throw new InitializationException(message,getSymbolicName()); } else { // create the new group and initialise tmpRangeItem = new RangeItem(); tmpRangeItem.RangeFrom = tmpRF; tmpRangeItem.RangeTo = tmpRT; tmpRangeItem.ValidityFrom = tmpVF; tmpRangeItem.ValidityTo = tmpVT; tmpRangeItem.Results = Results; GroupCache.put(Group, tmpRangeItem); } } /** * Get an object from the Cache, using the number and the date * * @param Group The group to search * @param rangeSearchValue The value to search for * @param UTCDate The date to search for * @return The return results */ public ArrayList<String> getEntryWithChildData(String Group, long rangeSearchValue, long UTCDate) { RangeItem tmpRangeItem; ArrayList<String> Value = null; // Get the Group tmpRangeItem = GroupCache.get(Group); // search for the right object while (tmpRangeItem != null) { if ((tmpRangeItem.RangeFrom <= rangeSearchValue) & (tmpRangeItem.RangeTo >= rangeSearchValue)) { // we have found a candidate - see if it is valid at the date if ((tmpRangeItem.ValidityFrom <= UTCDate) && (tmpRangeItem.ValidityTo > UTCDate)) { // found it! return tmpRangeItem.Results; } } // Move down the map tmpRangeItem = tmpRangeItem.nextRange; } return Value; } /** * Get an object from the Cache, using the number and the date * * @param Group The group to search * @param rangeSearchValue The value to search for * @param UTCDate The date to search for * @return The return result */ public String getEntry(String Group, long rangeSearchValue, long UTCDate) { RangeItem tmpRangeItem; // Get the Group tmpRangeItem = GroupCache.get(Group); // search for the right object while (tmpRangeItem != null) { if ((tmpRangeItem.RangeFrom <= rangeSearchValue) & (tmpRangeItem.RangeTo >= rangeSearchValue)) { // we have found a candidate - see if it is valid at the date if ((tmpRangeItem.ValidityFrom <= UTCDate) && (tmpRangeItem.ValidityTo > UTCDate)) { // found it! return tmpRangeItem.Results.get(0); } } // Move down the map tmpRangeItem = tmpRangeItem.nextRange; } return NO_RANGE_MATCH; } /** * Load the data from the defined file * * @throws InitializationException */ @Override public void loadDataFromFile() throws InitializationException { // Variable declarations int ObjectLinesLoaded = 0; BufferedReader inFile; String tmpFileRecord; String[] ObjectSplitFields; int Index; int tmpFieldCount = 0; String tmpGroup; long tmpRangeFrom; long tmpRangeTo; long tmpValidityFrom; long tmpValidityTo; // Try to open the file try { inFile = new BufferedReader(new FileReader(cacheDataFile)); } catch (FileNotFoundException ex) { message = "Application is not able to read file : <" + cacheDataFile + ">"; 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 { ObjectLinesLoaded++; ObjectSplitFields = tmpFileRecord.split(";"); // Set/Check the field count if (tmpFieldCount == 0) { // set it tmpFieldCount = ObjectSplitFields.length; // Check that it is valid - we cannot accept less than 4 fields if(tmpFieldCount < 4) { message = "The data must have >= 4 fields. We got <" + tmpFieldCount + "> fields in this record <" + tmpFileRecord + ">"; throw new InitializationException(message,getSymbolicName()); } } else { // Check that we remain consistent if (ObjectSplitFields.length != tmpFieldCount) { message = "The data must have <" + tmpFieldCount + "> fields. This record <" + tmpFileRecord + "> does not conform."; throw new InitializationException(message,getSymbolicName()); } } // parse the input tmpGroup = ObjectSplitFields[0]; tmpRangeFrom = Long.parseLong(ObjectSplitFields[1]); tmpRangeTo = Long.parseLong(ObjectSplitFields[2]); tmpValidityFrom = Long.parseLong(ObjectSplitFields[3]); tmpValidityTo = Long.parseLong(ObjectSplitFields[4]); ArrayList<String> tmpResults = new ArrayList<>(); for (Index = 5 ; Index < ObjectSplitFields.length ; Index++) { tmpResults.add(ObjectSplitFields[Index]); } // Add into the cache addEntry(tmpGroup,tmpRangeFrom,tmpRangeTo,tmpValidityFrom,tmpValidityTo,tmpResults); // Update to the log file if ((ObjectLinesLoaded % loadingLogNotificationStep) == 0) { message = "Number Range Data Loading: <" + ObjectLinesLoaded + "> configurations loaded for <" + getSymbolicName() + "> from <" + cacheDataFile + ">"; OpenRate.getOpenRateFrameworkLog().info(message); } } } } catch (IOException ex) { OpenRate.getOpenRateFrameworkLog().fatal( "Error reading input file <" + cacheDataFile + "> in record <" + ObjectLinesLoaded + ">. IO Error."); } catch (ArrayIndexOutOfBoundsException ex) { OpenRate.getOpenRateFrameworkLog().fatal( "Error reading input file <" + cacheDataFile + "> in record <" + ObjectLinesLoaded + ">. Malformed Record."); } finally { try { inFile.close(); } catch (IOException ex) { OpenRate.getOpenRateFrameworkLog().error( "Error closing input file <" + cacheDataFile + ">", ex); } } OpenRate.getOpenRateFrameworkLog().info( "Number Range Data Loading completed. <" + ObjectLinesLoaded + "> configuration lines loaded from <" + cacheDataFile + ">"); } /** * Load the data from the defined Data Source */ @Override public void loadDataFromDB() throws InitializationException { int Index; ResultSetMetaData Rsmd; int ColumnCount; int ObjectLinesLoaded = 0; String tmpGroup; long tmpRangeFrom; long tmpRangeTo; long tmpValidityFrom; long tmpValidityTo; // Find the location of the zone configuration file OpenRate.getOpenRateFrameworkLog().info("Starting Zone Cache Loading from DB"); // 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 retieving Indexed Matchdata"; throw new InitializationException(message,ex,getSymbolicName()); } // loop through the results for the customer login cache try { Rsmd = mrs.getMetaData(); ColumnCount = Rsmd.getColumnCount(); mrs.beforeFirst(); while (mrs.next()) { ObjectLinesLoaded++; tmpGroup = mrs.getString(1); tmpRangeFrom = mrs.getLong(2); tmpRangeTo = mrs.getLong(3); tmpValidityFrom = mrs.getLong(4); tmpValidityTo = mrs.getLong(5); ArrayList<String> tmpResults = new ArrayList<>(); for (Index = 6 ; Index <= ColumnCount ; Index++) { tmpResults.add(mrs.getString(Index)); } // Add into the cache addEntry(tmpGroup,tmpRangeFrom,tmpRangeTo,tmpValidityFrom,tmpValidityTo,tmpResults); // Update to the log file if ((ObjectLinesLoaded % loadingLogNotificationStep) == 0) { message = "Number Range Data Loading: <" + ObjectLinesLoaded + "> 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( "Number Range Data Loading completed. <" + ObjectLinesLoaded + "> configuration lines loaded from <" + cacheDataSourceName + ">"); } /** * Load the data from the defined Data Source Method */ @Override public void loadDataFromMethod() throws InitializationException { throw new InitializationException("Not implemented yet",getSymbolicName()); } // ----------------------------------------------------------------------------- // ------------- 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_OBJECT_COUNT, ClientManager.PARAM_DYNAMIC); ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_GROUP_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) { int ResultCode = -1; Collection<String> tmpGroups; Iterator<String> GroupIter; String tmpGroupName; int Objects = 0; RangeItem tmpRangeItem; // Return the number of objects in the cache if (Command.equalsIgnoreCase(SERVICE_GROUP_COUNT)) { return Integer.toString(GroupCache.size()); } // Return the number of objects in the cache if (Command.equalsIgnoreCase(SERVICE_OBJECT_COUNT)) { tmpGroups = GroupCache.keySet(); GroupIter = tmpGroups.iterator(); while (GroupIter.hasNext()) { tmpGroupName = GroupIter.next(); // Count the elements in the group tmpRangeItem = GroupCache.get(tmpGroupName); while (tmpRangeItem != null) { Objects++; tmpRangeItem = tmpRangeItem.nextRange; } } 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(); } }