/* ==================================================================== * 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.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. This is particularly useful for, for example * finding the customer that was using a router port at a given moment. * * This cache differs from the normal ValiditySegmentCache in that it allows * multiple periods of validity at the same moment, meaning that a lookup * can return multiple results. * * The format of the data, whether it is from a file or a DB is: * GROUP;SHARED_RESOURCE_ID;START;END;KEY1;....;KEYN * GROUP;SHARED_RESOURCE_ID;START;END;KEY1;....;KEYN * ... * * The "START" and "END" fields are in Unix time format: * * e.g.RADIUS;PORT_10_3;1159981449;1159983125;CBA1023452 * e.g.RADIUS;PORT_12_0;1159783248;1159823217;CBA1334467 * ... * * The data can be loaded either from a file, or from a DB. The properties that * must be configured for these are slightly different in each case. * * Loading from a file: * -------------------- * Define "DataSoureceType" as "File" * Define "DataFile" to point to the (relative or absolute) location of the * file to load * * CacheableClass.0.ClassName=OpenRate.cache.MultipleValidityCache * CacheableClass.0.ModuleName=PortCache * PortCache.DataSourceType=File * PortCache.DataFile=ConfigData/Router/PortMapFile.dat * * 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 * * CacheableClass.0.ClassName=OpenRate.cache.MultipleValidityCache * CacheableClass.0.ModuleName=PortCache * PortCache.DataSourceType=DB * PortCache.DataSource=LookupDataSource * PortCache.SelectStatement=select "DEFAULT",PORT_DESC,VALID_FROM,VALID_TO,CBA_ID from PORT_TAB */ public class MultipleValidityCache extends AbstractSyncLoaderCache { // This stores the index to all the groups. private 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; long timeTo; 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 MultipleValidityCache() { super(); GroupCache = new HashMap<>(50); } /** * 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 endTime The end time of the validity * @param results The list of result values */ public void addEntry(String group, String resourceID, long startTime, long endTime, 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, checking only the start // time (thuis avoiding the overlap detection if (!tmpResourceCache.containsKey(resourceID)) { // Create the new list tmpValidityNode = new ValidityNode(); tmpValidityNode.timeFrom = startTime; tmpValidityNode.timeTo = endTime; 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 entries until we get to the end while (tmpValidityNode.child != null) { tmpValidityNode = tmpValidityNode.child; } // add the node - we don't care about order newNode = new ValidityNode(); // move the information over newNode.timeFrom = startTime; newNode.timeTo = endTime; newNode.results = results; newNode.child = null; // add the new information to the tail of the list tmpValidityNode.child = newNode; } } /** * Returns the first 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 getFirstValidityMatch(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 ((tmpValidityNode.timeFrom <= time) & (tmpValidityNode.timeTo > time)) { return tmpValidityNode.results.get(0); } // Move down the map tmpValidityNode = tmpValidityNode.child; } } return NO_VALIDITY_MATCH; } /** * Returns the result 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> getFirstValidityMatchWithChildData(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 ((tmpValidityNode.timeFrom <= time) & (tmpValidityNode.timeTo > time)) { return tmpValidityNode.results; } // Move down the map tmpValidityNode = tmpValidityNode.child; } } return Value; } /** * Returns the vector of all matches to 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> getAllValidityMatches(String group, String resourceID, long time) { HashMap<String,ValidityNode> tmpResourceCache; ValidityNode tmpValidityNode; ArrayList<String> returnValue = new ArrayList<>(); // 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 ((tmpValidityNode.timeFrom <= time) & (tmpValidityNode.timeTo > time)) { // Add the value to the results list returnValue.add(tmpValidityNode.results.get(0)); } // Move down the map tmpValidityNode = tmpValidityNode.child; } } return returnValue; } /** * Returns the vector of all matches to 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<ArrayList<String>> getAllValidityMatchesWithChildData(String group, String resourceID, long time) { HashMap<String,ValidityNode> tmpResourceCache; ValidityNode tmpValidityNode; ArrayList<ArrayList<String>> returnValue = new ArrayList<>(); // 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 ((tmpValidityNode.timeFrom <= time) & (tmpValidityNode.timeTo > time)) { // Add the value to the results list returnValue.add(tmpValidityNode.results); } // Move down the map tmpValidityNode = tmpValidityNode.child; } } return returnValue; } // ----------------------------------------------------------------------------- // ------------------ Start of inherited Plug In functions --------------------- // ----------------------------------------------------------------------------- /** * Load the data from the defined file */ @Override public synchronized void loadDataFromFile() throws InitializationException { // Variable declarations int validityPeriodsLoaded = 0; BufferedReader inFile; String tmpFileRecord; String[] zoneFields; String group; String resourceID; long timeFrom; long timeTo; ArrayList<String> result; int idx; String tmpStartDate = null; String tmpEndDate = null; // Find the location of the zone configuration file OpenRate.getOpenRateFrameworkLog().info("Starting Multiple Validity 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]; tmpEndDate = zoneFields[3]; timeFrom = (int) fieldInterpreter.convertInputDateToUTC(tmpStartDate); timeTo = (int) fieldInterpreter.convertInputDateToUTC(tmpEndDate); // Interpret 0 values if (timeFrom == 0) timeFrom = CommonConfig.LOW_DATE; if (timeTo == 0) timeFrom = CommonConfig.HIGH_DATE; // now make an ArrayList of the results result = new ArrayList<>(); for (idx = 4 ; idx < zoneFields.length ; idx++) { result.add(zoneFields[idx]); } // Interpret 0 values if (timeFrom == 0) { timeFrom = CommonConfig.LOW_DATE; } if (timeTo == 0) { timeTo = CommonConfig.HIGH_DATE; } addEntry(group.intern(), resourceID, timeFrom, timeTo, result); // Update to the log file if ((validityPeriodsLoaded % loadingLogNotificationStep) == 0) { message = "Multiple Validity 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 + ">, <" + tmpEndDate + ">"; 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( "Multiple Validity Map Data Loading completed. " + validityPeriodsLoaded + " configuration lines loaded from <" + cacheDataFile + ">"); } /** * Load the data from the defined Data Source. This method is synchronised to avoid re-entrancy * problems with auto-loading. */ @Override public synchronized void loadDataFromDB() throws InitializationException { int validityPeriodsLoaded = 0; String group; String resourceID; long timeFrom; long timeTo; ArrayList<String> result; int idx; String tmpStartDate = null; String tmpEndDate = null; // Find the location of the zone configuration file OpenRate.getOpenRateFrameworkLog().info("Starting Multiple Validity Cache Loading from DB in <" + getSymbolicName() + ">"); try { // 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 Multiple Validity 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); tmpEndDate = mrs.getString(4); timeFrom = (int) fieldInterpreter.convertInputDateToUTC(tmpStartDate); timeTo = (int) fieldInterpreter.convertInputDateToUTC(tmpEndDate); // Interpret 0 values if (timeFrom == 0) timeFrom = CommonConfig.LOW_DATE; if (timeTo == 0) timeTo = CommonConfig.HIGH_DATE; // now make an ArrayList of the results result = new ArrayList<>(); for (idx = 5 ; idx <= mrs.getMetaData().getColumnCount() ; idx++) { result.add(mrs.getString(idx)); } // Add the map addEntry(group.intern(), resourceID, timeFrom, timeTo, result); // Update to the log file if ((validityPeriodsLoaded % loadingLogNotificationStep) == 0) { message = "Multiple Validity Data Loading: <" + validityPeriodsLoaded + "> configurations loaded for <" + getSymbolicName() + "> from <" + cacheDataSourceName + ">"; OpenRate.getOpenRateFrameworkLog().info(message); } } } catch (SQLException ex) { message = "Error opening Multiple Validity 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 + ">, <" + tmpEndDate + ">. 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 Multiple Validity Match Data for <" + getSymbolicName() + ">. message = <" + ex.getMessage() +">"; OpenRate.getOpenRateFrameworkLog().fatal(message); throw new InitializationException(message,getSymbolicName()); } } finally { DBUtil.close(mrs); DBUtil.close(StmtCacheDataSelectQuery); DBUtil.close(JDBCcon); } OpenRate.getOpenRateFrameworkLog().info( "Multiple Validity Map Data Loading completed. " + validityPeriodsLoaded + " configuration lines loaded from <" + cacheDataSourceName + ">"); } /** * Load the data from the defined Data Source Method */ @Override public void loadDataFromMethod() throws InitializationException { // Variable declarations int validityPeriodsLoaded = 0; int formFactor; String group; String resourceID; long timeFrom; long timeTo; ArrayList<String> result; int idx; ArrayList<String> tmpMethodResult; // Find the location of the zone configuration file OpenRate.getOpenRateFrameworkLog().info("Starting Multiple Validity 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 = Long.valueOf(tmpMethodResult.get(2)); timeTo = Long.valueOf(tmpMethodResult.get(3)); // Interpret 0 values if (timeFrom == 0) timeFrom = CommonConfig.LOW_DATE; if (timeTo == 0) timeTo = CommonConfig.HIGH_DATE; // now make an ArrayList of the results result = new ArrayList<>(); for (idx = 4 ; idx < tmpMethodResult.size() ; idx++) { result.add(tmpMethodResult.get(idx)); } // deal with high dates if (timeTo == 0) { timeTo = CommonConfig.HIGH_DATE; } addEntry(group.intern(), resourceID, timeFrom, timeTo, result); // Update to the log file if ((validityPeriodsLoaded % loadingLogNotificationStep) == 0) { message = "Multiple Validity Data Loading: <" + validityPeriodsLoaded + "> configurations loaded for <" + getSymbolicName() + "> from <" + cacheDataSourceName + ">"; OpenRate.getOpenRateFrameworkLog().info(message); } } OpenRate.getOpenRateFrameworkLog().info( "Multiple Validity 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. * */ @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(); } }