/* ====================================================================
* 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;
import OpenRate.configurationmanager.ClientManager;
import OpenRate.configurationmanager.EventHandler;
import OpenRate.configurationmanager.IEventInterface;
import OpenRate.exception.ExceptionHandler;
import OpenRate.exception.InitializationException;
import OpenRate.exception.ProcessingException;
import OpenRate.logging.AbstractLogFactory;
import OpenRate.logging.ILogger;
import OpenRate.logging.LogUtil;
import OpenRate.resource.CacheFactory;
import OpenRate.resource.IResource;
import OpenRate.resource.ResourceContext;
import OpenRate.resource.ResourceLoaderThread;
import OpenRate.transaction.ISyncPoint;
import OpenRate.utils.PropertyUtils;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
/**
* The OpenRate Daemon application is the main class that is responsible for
* running OpenRate applications in Daemon Mode (meaning that it runs forever
* until it is stopped by user intervention). You can also run applications in
* Batch Mode (meaning that the application will start, process any input and
* then close down). This class is responsible for:<br>
* - Creating the framework class<br>
* - Creating the resources<br>
* - Initialising the resources<br>
* - Creating the pipeline (Input, Output and Processing modules)<br>
* - Initialising all of the modules in the pipeline<br>
* - running the pipeline<br>
* - reporting status when the pipeline finishes<br>
* - Managing the pipeline sync between multiple pipes for external events that
* require all pipes to be inactive before they can be processed (e.g. reloads
* of data resource class data)
*
* <p>
* This is a brief description of how the sync works:<br>
* 1) When a pipeline receives an event that requires a sync point (for example
* a shared data resource needs to be reloaded), it flags the need by setting
* the sync status to 1 (=SyncFlag)<br>
* 2) The framework polls the status and sees that a SyncFlag has been set. It
* responds by setting the sync status of the requesting pipeline to 2
* (=SyncRequest). It also sets the sync flag of other pipelines to 2 as
* well.<br>
* 3) Any pipeline that has a SyncRequest has the obligation to stop processing
* after finishing the current transaction. When the transaction has been
* closed, it sets the sync status to 3 (=SyncReached)<br>
* 4) When all pipelines have come to the status "SyncReached", the framework
* sets the synch status of all pipelines to 4 (=SyncProcess). Any pipeline that
* has pending events now processes them<br>
* 5) When the event processing has finished, each pipeline sets the sync status
* to 5 (=SyncDone).<br>
* 6) When all pipelines reach "SyncDone" the framework sets the sync status of
* all pipelines back to 0, and the sync process finishes<br>
*/
public class OpenRate
implements IEventInterface,
Runnable {
/**
* Returns the application version string.
*
* @return the applicationVersionString
*/
public static String getApplicationVersionString() {
return applicationVersionString;
}
/**
* Access to the Framework AstractLogger. All non-pipeline specific messages
* (e.g. from resources or caches) should go into this log, as well as startup
* and shutdown messages. Normally the messages will be application driven,
* not stack traces, which should go into the error log.
*/
private ILogger fwLog = null;
// Access to the Error AstractLogger. All exception stack traces should go here.
private ILogger errorLog = null;
//Access to the Statistics AstractLogger. All statistics info should go here.
private ILogger statsLog = null;
/**
* Return code for the OpenRate instance.
*
* Default return code for a successful completion
*/
public static final int SUCCESS = 0;
/**
* Return code for the OpenRate instance.
*
* Default return code for a successful completion
*/
public static final int VERSION_FILE_NOT_FOUND = -4;
/**
* Return code for the OpenRate instance.
*
* Default return code for a successful completion
*/
public static final int PROPERTIES_FILE_NOT_FOUND = -5;
/**
* Return code for the OpenRate instance.
*
* Default return code for a successful completion
*/
public static final int WRONG_ARGUMENTS = -3;
/**
* Return code for the OpenRate instance.
*
* Default return code for a successful completion, but with some exception
*/
public static final int SUCCESS_WITH_EXCEPTION = 1;
/**
* Return code for the OpenRate instance.
*
* Default return code for failure due to a critical error
*/
public static final int FATAL_EXCEPTION = 2;
// List of Services that this Client supports
private final String SERVICE_FRAMEWORK_STOP = "Shutdown";
private final String SERVICE_SYNC_STATUS = "SyncStatus";
private final String SERVICE_SEQUENTIAL_LOADING = "SequentialLoading";
// This holds all of the pipelines that we are dealing with in this framework
private static HashMap<String, IPipeline> pipelineMap;
// This holds all of the resources that can call for a sync point
private static HashMap<String, IEventInterface> syncPointResourceMap;
// This is the application name, used for information only
private String applicationName = null;
// Pipeline pointer for performing pipeline related maniputions
private IPipeline tmpPipeline;
private Collection<String> pipelineSet = null;
private Iterator<String> pipelineIter = null;
private ThreadGroup pipelineThreadGroup;
// Context for managing the framework resources
private ResourceContext resourceContext = new ResourceContext();
private Collection<String> resourceSet = null;
private Iterator<String> resourceIter = null;
// For managing pipeline synchronisation
private boolean syncRequested = false;
private boolean syncReached;
private boolean syncFinished;
// the sync status, which is used to control sync processing
private int syncStatus = ISyncPoint.SYNC_STATUS_NORMAL_RUN;
// controls whether resources are loaded sequentially or in parallel
private boolean sequentialLoading = true;
// For managing semaphore files
private EventHandler ECI = null;
// For managing the auto reload function
private CacheFactory cacheFactory = null;
// Concrete classes for the following member attributes are
// loaded from a property file and instantiated via reflection.
private ExceptionHandler frameworkExceptionHandler = new ExceptionHandler();
// module symbolic name: set during initalisation
private String symbolicName = "Framework";
// The application object - split out like this so we can embed OpenRate if we need to
// and perform unit tests
private static OpenRate appl;
// Used to help people with fat fingers not shut the process down by accident
private int shutdownHookCalls = 0;
// Global application version
private static String applicationVersionString;
// used to simplify logging and error handling
private String message;
// value to show if the framework has active processing pipelines or not.
// This is true all the time that there are some pipes active
private boolean pipelinesActive = false;
// Shows if the framework is active
private static boolean frameworkActive = false;
/**
* default constructor
*/
public OpenRate() {
// Initialise the pipeline index
pipelineMap = new HashMap<>(50);
// Initialise the map of the event aware resources (for sync point handling)
syncPointResourceMap = new HashMap<>(50);
}
/**
* Main() - create the framework and the pipelines, and then execute them. If
* you need to have access to a detached OpenRate object, you can use do so by
* following the schema here: - Create the OpenRate object - call
* "createApplication" - call the "run" method - when finished, call the
* "finaliseApplication"
*
* @param args The arguments to pass to the process
*/
public static void main(String[] args) {
int status;
// Create the application - this initialises the entire system
appl = OpenRate.getApplicationInstance();
frameworkActive = true;
if (appl == null) {
System.err.println("Could not get OpenRate instance");
System.exit(-6);
}
// Create the application
status = appl.createApplication(args);
// run it if we created the app correctly
if (status == SUCCESS) {
appl.run();
}
// print shutdown message
appl.finaliseApplication();
frameworkActive = false;
// Bye bye, please come back soon
System.exit(status);
}
/**
* Check that the command line parameters are correct.
*
* @param args the passed command line parameters
* @return 0 if OK, otherwise a return code
*/
public int checkParameters(String[] args) {
// Check the arguments - we expect "-p testfile.properties.xml"
if ((args.length == 2) && (args[0].trim().equals("-p"))) {
// Check that the file exists
URL propertiesFile = getClass().getResource("/" + args[1]);
if (propertiesFile == null) {
System.out.println("Properties file <" + args[1] + "> not found on the class path. Aborting.");
return PROPERTIES_FILE_NOT_FOUND;
}
System.out.println("Found properties file <" + propertiesFile.getFile() + ">.");
} else {
// Arguments are not what we want
System.out.println("Command line not given correctly. Aborting.");
System.out.println(" Usage: java -cp $CLASSPATH OpenRate.OpenRate -p <properties-file.properties.xml>");
return WRONG_ARGUMENTS;
}
// All OK
return SUCCESS;
}
/**
* Get the properties file name from the command line parameters.
*
* @param args the passed command line parameters
* @return the file name as a URL, otherwise null
*/
public URL getPropertiesFileName(String[] args) {
URL propertiesFile = getClass().getResource("/" + args[1]);
return propertiesFile;
}
/**
* Get the global version from the version file.
*
* @return the application version
*/
public String getApplicationVersion() {
boolean foundVersion = false;
boolean foundBuild = false;
boolean foundDate = true;
String versionID = null;
String buildVer = null;
String buildDate = null;
InputStream input;
URL versionResourceFile = getClass().getResource("/VersionFile.txt");
if (versionResourceFile == null) {
return null;
}
input = getClass().getResourceAsStream("/VersionFile.txt");
java.util.Scanner s = new java.util.Scanner(input).useDelimiter("\\A");
while (s.hasNext()) {
String result = s.nextLine();
if (result.startsWith("OPENRATE_VERSION:")) {
foundVersion = true;
versionID = result.replaceAll("OPENRATE_VERSION:", "").trim();
}
if (result.startsWith("BUILD_VERSION:")) {
foundBuild = true;
buildVer = result.replaceAll("BUILD_VERSION:", "").trim();
}
if (result.startsWith("BUILD_DATE:")) {
foundDate = true;
buildDate = result.replaceAll("BUILD_DATE:", "").trim();
}
}
if (foundVersion && foundBuild && foundDate) {
return CommonConfig.PROG_NAME + " V" + versionID + " (" + buildDate + ")";
} else {
return null;
}
}
/**
* Get the GIT hash from the version file. This is logged but not displayed.
*
* @return the Git hash
*/
public String getBuildHash() {
boolean foundVersion = false;
boolean foundBuild = false;
boolean foundDate = true;
String versionID = null;
String buildVer = null;
String buildDate = null;
InputStream input;
URL versionResourceFile = getClass().getResource("/VersionFile.txt");
if (versionResourceFile == null) {
return null;
}
input = getClass().getResourceAsStream("/VersionFile.txt");
java.util.Scanner s = new java.util.Scanner(input).useDelimiter("\\A");
while (s.hasNext()) {
String result = s.nextLine();
if (result.startsWith("OPENRATE_VERSION:")) {
foundVersion = true;
versionID = result.replaceAll("OPENRATE_VERSION:", "").trim();
}
if (result.startsWith("BUILD_VERSION:")) {
foundBuild = true;
buildVer = result.replaceAll("BUILD_VERSION:", "").trim();
}
if (result.startsWith("BUILD_DATE:")) {
foundDate = true;
buildDate = result.replaceAll("BUILD_DATE:", "").trim();
}
}
if (foundVersion && foundBuild && foundDate) {
return "OpenRate Build " + buildVer;
} else {
return null;
}
}
/**
* Creates the OpenRate application. This is primarily here so that the
* OpenRate core can be launched in embedded mode.
*
* @param args The command line arguments
* @return The exit code
*/
public int createApplication(String[] args) {
int status;
ArrayList<String> pipelineList;
String tmpPipelineToCreate;
boolean initError = false;
// *********************** Initialization Block ****************************
// Set the version string
applicationVersionString = getApplicationVersion();
// Check that the parameters we got are formally correct
status = checkParameters(args);
// Check if we could find it
if (status != 0) {
// We could not locate the properties file
return status;
}
// Get the properties file
URL propertiesFileName = getPropertiesFileName(args);
// Check if we could find it
if (propertiesFileName == null) {
// We could not locate the properties file
return status;
}
// Load it
try {
PropertyUtils.getPropertyUtils().loadPropertiesXML(propertiesFileName, "FWProps");
} catch (InitializationException ex) {
System.err.println("Error loading properties file <" + propertiesFileName + ">. Aborting.");
return PROPERTIES_FILE_NOT_FOUND;
}
// Prepare the framework environment - Get the default logger until we
// read the properties file to get the correct logger
loadDefaultLogger();
// Start the dialogue with the user
System.out.println("");
System.out.println("--------------------------------------------------------");
System.out.println(" " + getApplicationVersionString());
System.out.println(" Copyright The OpenRate Project, 2005-2015");
System.out.println("--------------------------------------------------------");
// Start up the framework and load the resources etc
if (startupFramework() == true) {
// Get a list of the pipelines that we are working with from the client
// Manager instance
pipelineList = PropertyUtils.getPropertyUtils().getGenericNameList("PipelineList");
// Now that we have finished our internal initialisation, create the
// pipelines
Iterator<String> pipelineIterator = pipelineList.iterator();
while (pipelineIterator.hasNext()) {
tmpPipelineToCreate = pipelineIterator.next();
System.out.println("Creating pipeline:<" + tmpPipelineToCreate + ">");
if (createPipeline(tmpPipelineToCreate) == false) {
initError = true;
// don't need to carry on
break;
}
}
// ************************* Execution Block *******************************
if (initError == false) {
// Attach shutdown hook
appl.attachShutDownHook();
// run the application, run() exits on closedown
System.out.println("Running...");
} else {
System.err.println("Error during framework startup. See Error Log for details. Aborting.");
status = FATAL_EXCEPTION;
}
} else {
// Dump the error log
checkFrameworkExceptions();
System.err.println("Error during framework startup. See Error Log for details. Aborting.");
status = FATAL_EXCEPTION;
}
return status;
}
/**
* Finalises the close down of the OpenRate application. Primarily here so
* that the OpenRate core can be embedded in other applications. This is
* called after the pipelines have stopped, and manages the process of
* unloading the resources and tidying up.
*/
public void finaliseApplication() {
// Close down any resources or modules gracefully
System.out.println("Shutting down applicaton <" + applicationName + ">");
// put a message into the log
if (getFwLog() != null) {
getFwLog().info(":::: OpenRate Stopped ::::");
}
// clean up resources and logs
cleanup();
System.out.println("Finished");
System.out.println("---------------------------------------------------");
// clean up the instance
appl = null;
}
/**
* Start application. Initialize any common framework components, and then
* call the abstract run() method to allow sub-classes to do the real
* processing.
*
* We pass arguments to define the configuration that we are using.
*
* This is a top level method, in which exception handling must be performed.
*
* @return true if the startup went OK, otherwise false
*/
public boolean startupFramework() {
try {
// Get a default logger just in case - we will overwrite this later
setFwLog(LogUtil.getLogUtil().getDefaultLogger());
getFwLog().info("Set default logger");
// Get the (completely useless but informative) application Name, and
// well, inform the user
//ApplicationName = PropertyUtils.getPropertyValueDef(resources,
applicationName = PropertyUtils.getPropertyUtils().getPropertyValueDef("Application", "Undefined");
System.out.println("Initialising application <" + applicationName + ">");
// Initialse the FWLog. This should be done before anything else so that
// we are able to get a record of all the messages that occur
System.out.println("Intialising log resource...");
resourceContext = new ResourceContext();
// Initialize the AstractLogger from the ResourceContext. This means that we will
// have access to the logging functionality in time for the resource
// and module intialisation
// if we fail to get the logger, there's not much we can do other than
// pass this up to the loader, because we can't log it...
loadResources(true);
// Intialise the logger from the resource context. This overwrites the
// default logger that was set up previously and rewire the logger in the
// resource context which was forced to use the default logger up until
// now
setFwLog(LogUtil.getLogUtil().getLogger("Framework"));
getFwLog().info(":::: OpenRate Started ::::");
// Log the version information
getFwLog().info("OpenRate version: " + getApplicationVersion() + " (" + getBuildHash() + ")");
// Initalise the error log - this is intended to log all stack trace
// type events, keeping the main output as clean and businesslike as possible.
setErrorLog(LogUtil.getLogUtil().getLogger("ErrorLog"));
// Initalise the stats log - this is intended to log all statistics
// information, dealing with performance and profiling
setStatsLog(LogUtil.getLogUtil().getLogger("Statistics"));
// Get the sequential loading flag
sequentialLoading = Boolean.valueOf(PropertyUtils.getPropertyUtils().getFrameworkPropertyValueDef(SERVICE_SEQUENTIAL_LOADING, "true"));
// Intialise the other resources in the framework so that these are
// available for the module initialisation
System.out.println("Initialising other resources...");
loadResources(false);
// register ourself as an event handler client
registerClientManager();
} catch (InitializationException iex) {
// last resort error handler
frameworkExceptionHandler.reportException(iex);
} catch (Throwable th) {
// even laster resort error handler
message = "Unexpected Throwable starting up Framework";
frameworkExceptionHandler.reportException(new InitializationException(message, getSymbolicName(), true, true, th));
}
// If we had an error starting up, tell the rest of the loading
if (getHandler().hasError()) {
return false;
} else {
return true;
}
}
/**
* Check if any exceptions occurred and deal with them. This is called at
* regular intervals to see if any exceptions were reported in the previous
* period
*
* @return true if there was an exception since the last check
*/
public boolean checkFrameworkExceptions() {
// check if there have been any errors in the threads, and if there
// have, pass the exception up
if (getHandler().hasError()) {
// Failure occurred, propagate the error
getFwLog().error("Exception thrown in module <" + getSymbolicName() + ">");
// report the exceptions to the ErrorLog
Iterator<Exception> excList = getHandler().getExceptionList().iterator();
while (excList.hasNext()) {
// Handle the exceptions in order
Exception tmpException = excList.next();
handleOpenRateException(tmpException);
}
// Clear down the list
getHandler().clearExceptions();
return true;
} else {
// No exceptions, continue
return false;
}
}
/**
* Handle, format and report top level initialisation exceptions.
*
* @param ex
*/
public void handleOpenRateException(Exception ex) {
String Message;
// see if we have an Error Log - if so report to it
if (ex instanceof InitializationException) {
Message = "InitializationException thrown. Message <" + ex.getMessage() + ">";
} else if (ex instanceof ProcessingException) {
Message = "ProcessingException thrown. Message <" + ex.getMessage() + ">";
} else {
Message = "Exception thrown. Message <" + ex.getMessage() + ">";
}
if (getErrorLog() != null) {
// We have an Error Log initialised, so use it
getErrorLog().fatal(Message, ex);
// Also send to system out
System.err.println(Message);
} else {
// else we have no Error, so give a minimum of feedback on the console
System.err.println("No Error Log defined! Please set the logging properties correctly!");
System.err.println(Message);
}
// see if we have the Framework log, and if yes, log to it
if (getFwLog() != null) {
// We have a FWLog initialised, so use it
getFwLog().fatal(Message);
} else {
// else we have no FWLog, so give a minimum of feedback on the console
System.err.println(Message);
}
}
/**
* Load the default logger for the startup procedure
*/
private void loadDefaultLogger() {
setFwLog(LogUtil.getLogUtil().getDefaultLogger());
}
/**
* Create the pipeline for the defined pipeline name, creating the input and
* output adapters, the processing modules in multi threaded mode.
*
* This is a top level method, in which exception handling must be performed.
*
* @param pipelineName The name of the pipeline we are creating
* @return true if the pipe was created OK, otherwise false
*/
public boolean createPipeline(String pipelineName) {
boolean retVal = true;
tmpPipeline = new Pipeline();
addPipelineToMap(pipelineName, tmpPipeline);
// Add the pipeline to the threadgroup
try {
tmpPipeline.init(pipelineName);
} catch (InitializationException ex) {
handleOpenRateException(ex);
System.err.println("Error during startup in pipeline <" + pipelineName + ">, message <" + ex.getMessage() + ">. See Error Log for more details.");
retVal = false;
}
return retVal;
}
/**
* Run the pipelines that we have created. Visit each in turn and run it. We
* monitor the pipeline status in a low cost loop, and when all pipes have
* stopped, we exit the application.
*
* This loop performs the following tasks (initialisation): - check if there
* are any pipelines defined. If not, close down. - create the pipeline
* threads and start them - get a list of resources where sync point handling
* will be monitored
*
* And while running, performs these tasks: - runs until all pipes have
* stopped - Check for a sync point - If a sync point is found, manage the
* processing - When a sync point is reached, process any semaphores - Do any
* auto loading that is needed - Go back to sleep again
*
* This is a top level method, in which exception handling must be performed.
*/
@Override
public void run() {
String tmpPipeName;
String tmpResourceName;
int Index;
Thread tmpPipeThread;
ISyncPoint[] tmpPipeList;
ISyncPoint tmpResource;
ISyncPoint[] tmpResourceList;
int pipesActive;
int resourcesManaged = 0;
try {
pipelineSet = pipelineMap.keySet();
pipelineIter = pipelineSet.iterator();
tmpPipeList = new IPipeline[pipelineSet.size()];
pipelineThreadGroup = new ThreadGroup("Pipelines");
// initialise the variables for managing the sync point status for recources
resourceSet = syncPointResourceMap.keySet();
resourceIter = resourceSet.iterator();
tmpResourceList = new ISyncPoint[resourceSet.size()];
// initialise the ECI link
ECI = (EventHandler) resourceContext.get(EventHandler.RESOURCE_KEY);
// initialise the cache factory link
cacheFactory = (CacheFactory) resourceContext.get(CacheFactory.RESOURCE_KEY);
if (!pipelineIter.hasNext()) {
// Seems the user has not defined any pipelines. Abort
getFwLog().error("No Pipelines defined. Closing down...");
} else {
// Prepare the main processing loop
Index = 0;
while (pipelineIter.hasNext()) {
tmpPipeName = pipelineIter.next();
tmpPipeline = pipelineMap.get(tmpPipeName);
tmpPipeList[Index] = tmpPipeline;
// Keep track of the pipeline threads we have
tmpPipeThread = new Thread(pipelineThreadGroup, tmpPipeline, "Pipeline-" + tmpPipeName);
// Start the thread - the pipeline threads are free running
tmpPipeThread.start();
Index++;
}
// Get the list of resources that need managing for sync points
Index = 0;
while (resourceIter.hasNext()) {
tmpResourceName = resourceIter.next();
if (syncPointResourceMap.get(tmpResourceName) instanceof ISyncPoint) {
tmpResourceList[resourcesManaged] = (ISyncPoint) syncPointResourceMap.get(tmpResourceName);
Index++;
// Because not all of the resources need managing, we have to
// keep track of only those that do
resourcesManaged++;
}
}
// Check the state of the pipelines
pipesActive = 1;
syncRequested = false;
// ********************** main processing loop **************************
while (pipesActive > 0) {
syncReached = true;
syncFinished = true;
for (Index = 0; Index < tmpPipeList.length; Index++) {
// Find out if one of the pipes is requesting a synch point
// This means that the other pipelines will be requested to stop
// accepting transactions, until all pipes are stopped, at which
// point the pipes will be ordered to do any pending event
// processing
tmpPipeline = (IPipeline) tmpPipeList[Index];
syncStatus = tmpPipeline.getSyncStatus();
// see if we have finished the sync - this is when all
// pipes report back that they have status SYNC_STATUS_RESTARTING
syncFinished &= (syncStatus == ISyncPoint.SYNC_STATUS_SYNC_FINISHED);
// see if we have reached the sync point - this is when all
// pipes report back that they have status SYNC_STATUS_SYNC_PROCESSING
syncReached &= (syncStatus == ISyncPoint.SYNC_STATUS_SYNC_REACHED);
if (syncStatus == ISyncPoint.SYNC_STATUS_SYNC_FLAGGED) {
// Mark that we are requesting a synch
syncRequested = true;
getFwLog().info("Sync point requested by pipeline <" + tmpPipeline.getSymbolicName() + ">");
// Confirm that we have got the message
tmpPipeline.setSyncStatus(ISyncPoint.SYNC_STATUS_SYNC_REQUESTED);
syncStatus = ISyncPoint.SYNC_STATUS_SYNC_REQUESTED;
}
// propogate the sync command - this might take two cycles to
// complete
if ((syncStatus == 0) & (syncRequested)) {
// propogate the sync message
tmpPipeline.setSyncStatus(ISyncPoint.SYNC_STATUS_SYNC_REQUESTED);
syncStatus = ISyncPoint.SYNC_STATUS_SYNC_REQUESTED;
System.out.println("Initiating Sync Processing...");
}
// see if we had an abort - this is caused by an exception in the pipe
// and we have configured the pipe to stop on exception
if (tmpPipeline.isAborted()) {
// bring the rest of the framework down
stopAllPipelines();
}
}
// Count the number of pipes active right now
pipesActive = pipelineThreadGroup.activeCount();
//pipelineThreadGroup.list();
// Update the framework active flag
pipelinesActive = (pipesActive > 0);
// Check if we have reached the synch point
if (syncReached) {
for (Index = 0; Index < tmpPipeList.length; Index++) {
// Update the pipe status
tmpPipeline = (IPipeline) tmpPipeList[Index];
// start the synch processing
tmpPipeline.setSyncStatus(ISyncPoint.SYNC_STATUS_SYNC_PROCESSING);
}
}
// Check if we are clearing down a synch point
if (syncFinished) {
for (Index = 0; Index < tmpPipeList.length; Index++) {
// Update the pipe status
tmpPipeline = (IPipeline) tmpPipeList[Index];
// start the record processing
tmpPipeline.setSyncStatus(ISyncPoint.SYNC_STATUS_NORMAL_RUN);
}
// clear down the request
syncRequested = false;
syncStatus = ISyncPoint.SYNC_STATUS_NORMAL_RUN;
getFwLog().debug("Running...");
System.out.println("Running...");
}
// See if any of the resources is requesting a sync point
for (Index = 0; Index < resourcesManaged; Index++) {
tmpResource = tmpResourceList[Index];
syncStatus = tmpResource.getSyncStatus();
if (syncStatus == ISyncPoint.SYNC_STATUS_SYNC_FLAGGED) {
// Mark that we are requesting a synch
syncRequested = true;
getFwLog().info("Sync point requested by resource <" + tmpResource.getSymbolicName() + ">");
// Confirm that we have got the message
tmpResource.setSyncStatus(ISyncPoint.SYNC_STATUS_SYNC_REQUESTED);
syncStatus = ISyncPoint.SYNC_STATUS_SYNC_REQUESTED;
}
// propogate the sync command - this might take two cycles to
// complete
if ((syncStatus == ISyncPoint.SYNC_STATUS_NORMAL_RUN) & (syncRequested)) {
// propogate the sync message
tmpResource.setSyncStatus(ISyncPoint.SYNC_STATUS_SYNC_REQUESTED);
syncStatus = ISyncPoint.SYNC_STATUS_SYNC_REQUESTED;
}
// Check if we have reached the synch point
if (syncReached) {
// start the processing
tmpResource.setSyncStatus(ISyncPoint.SYNC_STATUS_SYNC_PROCESSING);
syncStatus = ISyncPoint.SYNC_STATUS_SYNC_PROCESSING;
}
// Check if we are clearing down a synch point
if (syncFinished) {
// finish the processing
tmpResource.setSyncStatus(ISyncPoint.SYNC_STATUS_NORMAL_RUN);
// clear down the request
syncRequested = false;
syncStatus = ISyncPoint.SYNC_STATUS_NORMAL_RUN;
}
// see if we have reached the sync point - this is when all
// pipes report back that they have status SYNC_STATUS_SYNC_PROCESSING
syncReached &= (syncStatus == ISyncPoint.SYNC_STATUS_SYNC_REACHED);
// see if we have finished the sync - this is when all
// pipes report back that they have status SYNC_STATUS_RESTARTING
syncFinished &= (syncStatus == ISyncPoint.SYNC_STATUS_SYNC_FINISHED);
}
// FWLog some stuff
if (syncReached) {
getFwLog().info("Sync point reached");
}
if (syncFinished) {
getFwLog().info("Sync point finished");
}
// Process any semaphore file ther is to process
ECI.processSemaphoreFile();
// Manage the auto reload
if ((cacheFactory == null) == false) {
cacheFactory.updateAutoReload();
}
// Go into a low processor cost loop to monitor the pipes. When the last
// stops, close down
if (pipelinesActive) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
// This can't happen, so NOP
}
}
}
// Destroy the threadgroup that held the pipes
pipelineThreadGroup.destroy();
pipelineThreadGroup = null;
// Log that we have finished with the pipes
getFwLog().debug("Stopped all pipes");
}
} catch (Exception ex) {
handleOpenRateException(ex);
}
}
/**
* Perform any cleanup required before the application exits. Cycle through
* all of the pipelines in the map and issue a shutdown command to each one of
* them.
*/
protected void closePipelines() {
if (pipelineSet != null) {
pipelineIter = pipelineSet.iterator();
while (pipelineIter.hasNext()) {
tmpPipeline = pipelineMap.get(pipelineIter.next());
System.out.println("Closing pipeline <" + tmpPipeline.getSymbolicName() + ">");
tmpPipeline.shutdownPipeline();
System.out.println("Destroying pipeline <" + tmpPipeline.getSymbolicName() + ">");
tmpPipeline.cleanupPipeline();
}
}
}
/**
* Perform any cleanup required by the application once the processing has
* been terminated and the pipelines have been closed.
*
* This method will also call the doCleanup() abstract method to allow
* concrete application classes to shutdown gracefully.
*/
public final void cleanup() {
// Close resources
closeResources();
// Clear out the exception handler
frameworkExceptionHandler.clearExceptions();
// Deallocate the properties object
PropertyUtils.closePropertyUtils();
// Clean up client map
ClientManager.getClientManager().clear();
}
/**
* Stop all pipelines in the framework. This method gives the command to stop
* the pipelines, but the completion of the stop will happen some time later
* (pipelines might be in the middle of processing a transaction, and they
* will finish this before stopping).
*/
public final void stopAllPipelines() {
// Set the shutdown flag
getFwLog().info("Shutdown command received. Shutting down pipelines as soon as possible.");
pipelineSet = pipelineMap.keySet();
pipelineIter = pipelineSet.iterator();
// Iterate through all the pipelines we have and tell them to stop
while (pipelineIter.hasNext()) {
tmpPipeline = pipelineMap.get(pipelineIter.next());
// markForShutdown tells the pipe to stop accepting new transactions
// and mark that a stop is pending. The pipeline will stop when all
// transactions are finished.
tmpPipeline.markForShutdown();
}
}
/**
* Shutdown hook. Attaching shutdown hook to the Application, so that we
* gracefully stop when the process is interrupted.
*/
private void attachShutDownHook() {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
if (shutdownHookCalls == 0) {
// Hook triggered, starting shutdown process
// Stop all pipes
stopAllPipelines();
getFwLog().info("Shutdown command called");
}
// increment the shutdown hook
shutdownHookCalls++;
// If the person shutting down is too insistent, we just abort
if (shutdownHookCalls == 3) {
getFwLog().error("Hard shutdown forced by 3 kills. Aborting.");
System.exit(-99);
}
}
});
}
/**
* Load the framework resources. Can be called in two different ways: 1) To
* load the logger only. Clearly the logger is very useful when loading other
* resources, so we come in and do a quick load just of the logger if
* LoadLoggerOnly is set to true 2) Load all the other resources: Once we have
* a logger, we load all the remaining resources. This skips the logger
* resource
*
* @param loggerOnly True if we are loading the logger, otherwise false
* @throws InitializationException
*/
private void loadResources(boolean loggerOnly) throws InitializationException {
IEventInterface tmpEventIntf;
String tmpResourceName = null;
String tmpResourceClassName;
Class<?> resourceClass;
IResource resource;
ArrayList<String> tmpResourceNameList;
ThreadGroup tmpGrpResource;
// Get the resource list
tmpResourceNameList = PropertyUtils.getPropertyUtils().getGenericNameList("Resource");
if (tmpResourceNameList == null || tmpResourceNameList.isEmpty()) {
frameworkExceptionHandler.reportException(new InitializationException("No resources defined. Aborting.", getSymbolicName()));
// we are done
return;
}
// Check if we got the Framework Log resource
if ((tmpResourceNameList.contains(AbstractLogFactory.RESOURCE_KEY) == false) & loggerOnly) {
// No FWLog resource.
frameworkExceptionHandler.reportException(new InitializationException("Log resource not found. Framework aborting.", getSymbolicName()));
return;
}
// Check if we got the ECI resource
if ((tmpResourceNameList.contains(EventHandler.RESOURCE_KEY) == false) & (!loggerOnly)) {
// No FWLog resource.
frameworkExceptionHandler.reportException(new InitializationException("ECI resource not found. Framework aborting.", getSymbolicName()));
return;
}
try {
// Iterate through the resources and create them
resourceIter = tmpResourceNameList.iterator();
// The thread group for holding the resources if we use multi-threaded loading
tmpGrpResource = new ThreadGroup("Resources");
while (resourceIter.hasNext() && (getHandler().hasError() == false)) {
// Get the next resource
tmpResourceName = resourceIter.next();
// If we are loading the log factory, just load the first, else load all but the first
if (((loggerOnly) & (tmpResourceName.equalsIgnoreCase(AbstractLogFactory.RESOURCE_KEY)))
| ((!loggerOnly) & (tmpResourceName.equalsIgnoreCase(AbstractLogFactory.RESOURCE_KEY) == false))) {
// This is where we will launch the resources as separate classes if
// necessary
tmpResourceClassName = PropertyUtils.getPropertyUtils().getResourcePropertyValue(tmpResourceName, "ClassName");
resourceClass = Class.forName(tmpResourceClassName);
resource = (IResource) resourceClass.newInstance();
System.out.println(" Initialising Resource <" + tmpResourceName + ">...");
// see if we are using sequential or threaded loading
// (Sequential is easier to use, but clearly takes longer to load)
if (sequentialLoading) {
// perform initialisation
resource.init(tmpResourceName);
// register the created resource with the context so we can find it later
resourceContext.register(tmpResourceName, resource);
// Now see if we have to register with the config manager
if (resource instanceof IEventInterface) {
// Register
tmpEventIntf = (IEventInterface) resource;
tmpEventIntf.registerClientManager();
// Add the resource to the list of the resources that can call for
// a sync point
syncPointResourceMap.put(tmpResourceName, tmpEventIntf);
}
} else {
// Multi-threaded loading - create a new thread for each resource
ResourceLoaderThread resourceLoaderThread = new ResourceLoaderThread(tmpGrpResource, tmpResourceName);
// Set up the thread with the information it will need
resourceLoaderThread.setResource(resource);
resourceLoaderThread.setResourceName(tmpResourceName);
resourceLoaderThread.setResourceContext(resourceContext);
resourceLoaderThread.setsyncPointResourceMap(syncPointResourceMap);
// Launch it
resourceLoaderThread.start();
}
}
}
// Close down the thread group if we have finished using it (all threads finished)
while (tmpGrpResource.activeCount() > 0) {
Thread.sleep(1000);
}
// Destroy the thread group
tmpGrpResource.destroy();
} catch (ClassNotFoundException ex) {
frameworkExceptionHandler.reportException(new InitializationException("ClassNotFoundException: "
+ "Class not found for Resource <"
+ tmpResourceName + ">", ex, getSymbolicName()));
} catch (InstantiationException ex) {
frameworkExceptionHandler.reportException(new InitializationException("InstantiationException: "
+ "No default constructor found for for Resource <"
+ tmpResourceName + ">", ex, getSymbolicName()));
} catch (IllegalAccessException ex) {
frameworkExceptionHandler.reportException(new InitializationException("IllegalAccessException: "
+ "Check that the Resource <"
+ tmpResourceName
+ "> has a public default constructor.",
ex, getSymbolicName()));
} catch (ClassCastException ex) {
frameworkExceptionHandler.reportException(new InitializationException("ClassCastException: "
+ "Class identified as Resource <"
+ tmpResourceName
+ "> does not implement "
+ "Resource interface.", ex, getSymbolicName()));
} catch (NullPointerException ex) {
frameworkExceptionHandler.reportException(new InitializationException("Null pointer exception creating Resource <"
+ tmpResourceName + ">", ex, getSymbolicName()));
} catch (InitializationException ex) {
// We don't need to nest/interpret this exception, just report it
frameworkExceptionHandler.reportException(ex);
} catch (OutOfMemoryError ex) {
frameworkExceptionHandler.reportException(new InitializationException("Out of memory creating <" + tmpResourceName + ">", getSymbolicName(), true, true, ex));
} catch (InterruptedException ex) {
frameworkExceptionHandler.reportException(new InitializationException("Interrupted exception creating Resource <"
+ tmpResourceName + ">", ex, getSymbolicName()));
} catch (Throwable th) {
frameworkExceptionHandler.reportException(new InitializationException("Unexpected exception creating Resource <"
+ tmpResourceName + ">", getSymbolicName(), true, true, th));
}
}
/**
* Unload the framework resources, using the priority order defined in the
* properties.
*/
private void closeResources() {
// shutdown any configured resources via ResourceContext.
System.out.println("Closing All Resources...");
resourceContext.cleanup();
// Sometimes we will have to wait for some resources to finish
while (resourceContext.isActive()) {
getFwLog().debug("Waiting 100mS for the resource context to stop");
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
}
}
// -----------------------------------------------------------------------------
// ------------- Start of inherited IEventInterface functions ------------------
// -----------------------------------------------------------------------------
/**
* Register ourselves with the event handler so that we can stop the framework
* gracefully.
*
* @throws OpenRate.exception.InitializationException
*/
@Override
public void registerClientManager() throws InitializationException {
IEventInterface tmpEventIntf;
//Register this Client
ClientManager.getClientManager().registerClient("Framework", getSymbolicName(), this);
//Register services for this Client
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_FRAMEWORK_STOP, ClientManager.PARAM_DYNAMIC);
ClientManager.getClientManager().registerClientService(getSymbolicName(), SERVICE_SYNC_STATUS, ClientManager.PARAM_NONE);
// Register each of the pipelines' services
pipelineSet = pipelineMap.keySet();
pipelineIter = pipelineSet.iterator();
while (pipelineIter.hasNext()) {
tmpEventIntf = (IEventInterface) pipelineMap.get(pipelineIter.next());
// Register the pipeline with the manager. The pipeline will register
// under its own name, so we pass null
tmpEventIntf.registerClientManager();
}
}
/**
* process control events.
*
* @param Command The command to process
* @param Init True if we are during framework init
* @param Parameter The parameter to process for the command
* @return The status of the processing
*/
@Override
public String processControlEvent(String Command, boolean Init,
String Parameter) {
int ResultCode = -1;
if (Command.equalsIgnoreCase(SERVICE_FRAMEWORK_STOP)) {
if (Parameter.equalsIgnoreCase("true")) {
stopAllPipelines();
} else {
return "false";
}
ResultCode = 0;
}
if (Command.equalsIgnoreCase(SERVICE_SYNC_STATUS)) {
return Integer.toString(syncStatus);
}
if (ResultCode == 0) {
getFwLog().debug(LogUtil.LogECIFWCommand(Command, Parameter));
return "OK";
} else {
return "Error: Command not understood.";
}
}
/**
* return the symbolic name
*
* @return The symbolic name for this plug in
*/
public String getSymbolicName() {
return symbolicName;
}
/**
* set the symbolic name
*
* @param Name The new symbolic name for this plug in
*/
public void setSymbolicName(String Name) {
symbolicName = Name;
}
/**
* Create the OpenRate application instance
*
* @return the instance
*/
public static OpenRate getApplicationInstance() {
if (appl == null) {
appl = new OpenRate();
}
return appl;
}
/**
* Get the framework exception handler.
*
* @return The framework exception handler
*/
public static ILogger getOpenRateFrameworkLog() {
if (appl != null) {
return appl.getFwLog();
} else {
return null;
}
}
/**
* Get the framework exception handler.
*
* @return The framework exception handler
*/
public static ILogger getOpenRateErrorLog() {
return appl.getErrorLog();
}
/**
* Get the framework exception handler.
*
* @return The framework exception handler
*/
public static ILogger getOpenRateStatsLog() {
return appl.getStatsLog();
}
/**
* Get the framework exception handler.
*
* @return The framework exception handler
*/
public static ExceptionHandler getFrameworkExceptionHandler() {
return appl.getHandler();
}
/**
* Return the Framework Exception handler.
*
* @return the handler
*/
public ExceptionHandler getHandler() {
return frameworkExceptionHandler;
}
/**
* @param handler the handler to set
*/
public void setHandler(ExceptionHandler handler) {
this.frameworkExceptionHandler = handler;
}
/**
* @return the fwLog
*/
public ILogger getFwLog() {
return fwLog;
}
/**
* @param fwLog the fwLog to set
*/
public void setFwLog(ILogger fwLog) {
this.fwLog = fwLog;
}
/**
* @return the errorLog
*/
public ILogger getErrorLog() {
return errorLog;
}
/**
* @param errorLog the errorLog to set
*/
public void setErrorLog(ILogger errorLog) {
this.errorLog = errorLog;
}
/**
* @return the statsLog
*/
public ILogger getStatsLog() {
return statsLog;
}
/**
* @param statsLog the statsLog to set
*/
public void setStatsLog(ILogger statsLog) {
this.statsLog = statsLog;
}
/**
* Get the reference to a pipeline.
*
* @param pipename The pipeline to get
* @return The pipeline reference
*/
public static IPipeline getPipelineFromMap(String pipename) {
return pipelineMap.get(pipename);
}
/**
* Insert a pipeline into the map. Used to unit tests, where we want to
* concentrate on the module we are testing. Normally the pipeline map is
* populated by the framework startup.
*
* @param pipename The pipeline to get
* @param pipeline the pipeline
*/
public static void addPipelineToMap(String pipename, IPipeline pipeline) {
pipelineMap.put(pipename, pipeline);
}
/**
* Returns the state of the processing
*
* @return the frameworkActive
*/
public boolean isFrameworkActive() {
return frameworkActive || pipelinesActive;
}
}