/**
* Redundance in stand-alone mode:
*
* When running in stand-alone mode, the proper way to switch is to set the currently
* in control process to monitor and then let the monitor process takes over.
*
* Two special cases could happen:
* - If we switch the monitor process to control without switching the control process,
* both process will be in control.
* - If we switch the control process when there is no monitor process running, the
* control process will switch to monitor and there is no way that it would switch
* back automatically. (i.e. do not expect the process to know that there is no
* monitor process thus switching back to control)
*/
package tcg.syscontrol;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import tcg.common.CorbaHelper;
import tcg.common.CorbaManager;
import tcg.common.DatabaseManager;
import tcg.common.LoggerManager;
import tcg.common.Utilities;
import tcg.common.DatabaseManager.DatabaseType;
import tcg.common.util.LoggerWriter;
import tcg.common.util.Native;
import tcg.syscontrol.cos.CosTerminationCodeEnum;
import tcg.syscontrol.cos.DEFAULT_MGR_PORT;
import tcg.syscontrol.cos.ICosManagedProcess;
import tcg.syscontrol.cos.ICosManagedProcessHelper;
import tcg.syscontrol.cos.ICosProcessManager;
import tcg.syscontrol.cos.ICosProcessManagerHelper;
import tcg.syscontrol.cos.LOG_FILE_KEY;
import tcg.syscontrol.cos.LOG_LEVEL_KEY;
//import tcg.syscontrol.cos.CENTRAL_DB_PASSWORD_KEY;
//import tcg.syscontrol.cos.CENTRAL_DB_TNSNAME_KEY;
//import tcg.syscontrol.cos.CENTRAL_DB_USERNAME_KEY;
//import tcg.syscontrol.cos.PRIMARY_DB_PASSWORD_KEY;
//import tcg.syscontrol.cos.PRIMARY_DB_TNSNAME_KEY;
//import tcg.syscontrol.cos.PRIMARY_DB_TYPE_KEY;
//import tcg.syscontrol.cos.PRIMARY_DB_USERNAME_KEY;
//import tcg.syscontrol.cos.STANDBY_DB_PASSWORD_KEY;
//import tcg.syscontrol.cos.STANDBY_DB_TNSNAME_KEY;
//import tcg.syscontrol.cos.STANDBY_DB_USERNAME_KEY;
import tcg.syscontrol.cos.CosLogLevelEnum;
import tcg.syscontrol.cos.CosOperationModeEnum;
import tcg.syscontrol.cos.CosPollException;
import tcg.syscontrol.cos.CosPollNotInModeException;
import tcg.syscontrol.cos.CosProcessStatusEnum;
import tcg.syscontrol.cos.CosProcessTypeEnum;
import tcg.syscontrol.cos.CosRunParamStruct;
import tcg.syscontrol.cos.ICosManagedProcessPOA;
public abstract class ManagedProcess extends ICosManagedProcessPOA
{
private static final String DEF_ENTITYNAME = "ManagedProcess";
private static final String DEF_CONFIGFILE = "scada.properties";
private static final int DEF_PEER_SYNCRATE = 1000; //in msec
//how often we print out the current state for debugging
protected static final int VERBOSE_STATE_DEBUGGING_COUNTER = 100;
//default error threshold
@SuppressWarnings("unused")
private static final short DEF_ERROR_THRESHOLD = 3;
//how many times I attempt to do failed corba operation
//NOTE: internally, jacorb has tried several times before reporting failure on a
// corba operation. so we do not need to try multiple times anymore
private static final int CORBA_ERROR_THRESHOLD = 1;
protected Logger logger = null;
protected LoggerWriter loggerWriter = null;
protected short weightage = 0;
protected ICosProcessManager processManager = null;
protected CosProcessStatusEnum state = CosProcessStatusEnum.StatUnstarted;
protected CosProcessStatusEnum requestedState = CosProcessStatusEnum.StatUnstarted;
protected Properties props = new Properties(); //contain whatever I got from process manager
//protected Properties configProps = new Properties(); //configuration properties
protected long pid = 0;
protected String entityName = DEF_ENTITYNAME;
protected int managerPort = DEFAULT_MGR_PORT.value;
protected String logFile = "";
protected String configFile = DEF_CONFIGFILE;
protected boolean hasManager = true;
protected String peerName = ""; //peer hostname (if running as stand-alone)
protected int peerPort = 0; //peer port number (if running as stand-alone)
protected boolean isPrimary = false; //run as primary (if running as stand-alone)
protected ICosManagedProcess peerProcess = null;
protected int peerSyncRate = DEF_PEER_SYNCRATE;
private PrimaryPollerThread peerPollingThread_ = new PrimaryPollerThread();
private StandbyNotifierThread peerNotifierThread_ = new StandbyNotifierThread();
//working directory
protected String workingDir = "";
//set this one if you want to have a persistent servant accessable via corbaloc
protected int corbaPort = 0;
//protected String corbaName = "";
protected java.lang.Object mutex = new java.lang.Object();
protected boolean keepRunning = false;
protected boolean isConfigured = false;
//the command line argument options
protected Options options = new Options();
//the main thread
protected Thread thread = null;
//if we want to start this as in-process thread, what we should do
public abstract void execute(String args);
//stop the in-process thread
public void stop()
{
requestedState = CosProcessStatusEnum.StatStopped;
//disable running flag
isConfigured = true;
keepRunning = false;
//trigger the main thread. only expect ONE main thread!!!
if (thread != null)
{
thread.interrupt();
}
}
/*
* Abstract Subroutines
*/
/*
* The child specific configure routines
*/
//called during initialization
protected abstract boolean initialize2(String[] args, Properties props);
//called after the process manager has sent runtime params
protected abstract boolean configureApplication2();
//called when we are going to/leaving control mode
protected abstract boolean startControl();
protected abstract boolean stopControl();
//called when we are going to/leaving monitor mode
protected abstract boolean startMonitor();
protected abstract boolean stopMonitor();
//get the status string
protected abstract String getStatusString();
//default ctor
public ManagedProcess()
{
//default contructor
}
//default dtor
protected void finalize() throws Throwable
{
CorbaManager.shutdown();
CorbaManager.cleanup();
}
protected static void printUsage()
{
System.out.println("Managed Process Command Line Parameters: ");
System.out.println(" -e | --entity <entity-name> Entity name (required)");
System.out.println(" -p | --pid <process-id> Process id");
System.out.println(" -f | --config-file <config-file> Configuration file");
System.out.println(" -m | --mgr-port <manager-port> Manager port");
System.out.println(" -l | --logfile <log-file-name> Log file path");
System.out.println(" -d | --working-dir <directory> Working directory");
System.out.println(" -cp | --corba-port <port-no> Corba port to bind to");
System.out.println(" -nm | --nomgr Run as stand-alone (no process manager)");
System.out.println(" -pn | --peer-name <host-name> Peer hostname (if running as stand-alone)");
System.out.println(" -pp | --peer-port <port-no> Peer port number (if running as stand-alone)");
System.out.println(" -pm | --primary Run as primary server (if running as stand-alone)");
System.out.println(" -h | --help Print out this help");
System.out.println(" -v | --version Print out program version");
System.out.println("");
System.out.println("Managed Process Java Properties (Configuration file or System properties): ");
System.out.println(" tcg.db.type Database type");
System.out.println(" tcg.db.name Database TNS name");
System.out.println(" tcg.db.user Database username");
System.out.println(" tcg.db.password Database password");
System.out.println(" tcg.db.encyrpted Whether the password is encrypted");
System.out.println(" tcg.event.server1 Primary event server");
System.out.println(" tcg.event.server2 Secondary event server");
}
public boolean initialize(String[] args)
{
state = CosProcessStatusEnum.StatStartup;
//parameters passed as environment properties
pid = __parseInt(System.getProperty("pid", "0"));
entityName = System.getProperty("entityName", DEF_ENTITYNAME);
configFile = System.getProperty("propertiesFile", DEF_CONFIGFILE);
//parse the arguments
if (!parseArguments(args))
{
state = CosProcessStatusEnum.StatUnstarted;
return false;
}
//reset the logging in case it is not yet done by child class
//otherwise, this function will do nothing
if (logFile.length() > 0)
{
LoggerManager.setLogFile(logFile);
}
//load all configuration
props = load_properties(configFile, props);
//load the database configuration
if (!configure_database_connection())
{
logger.error("Can not configure database connnection.");
state = CosProcessStatusEnum.StatUnstarted;
return false;
}
//specific initialization for child class
if (!initialize2(args, props))
{
logger.error("Can not initialize child process.");
state = CosProcessStatusEnum.StatUnstarted;
return false;
}
//initialize corba manager. if corbaPort = 0, the port is randomly allocated
//if it is already initialized by the child class, it does nothing
if (!CorbaManager.initialize(corbaPort, managerPort))
{
logger.error("Can not initialize CORBA manager.");
state = CosProcessStatusEnum.StatUnstarted;
return false;
}
//activate this servant.
//if corbaPort != "", the servant is created as persistent object and
// can be acessed via corbaloc address: corbaloc::<ip-addr>:<port>/<corba-name>
if (!CorbaManager.activate(this, entityName))
{
logger.error("Can not activate CORBA servant.");
state = CosProcessStatusEnum.StatUnstarted;
return false;
}
//make sure we have a valid manager port
if (hasManager)
{
if (managerPort == 0)
{
managerPort = DEFAULT_MGR_PORT.value;
}
//register with process manager
if (!register_with_process_manager())
{
//failed. do not continue.
logger.error( "Couldn't register with Process Manager!" );
state = CosProcessStatusEnum.StatUnstarted;
return false;
}
//wait for runtime params from process manager
requestedState = CosProcessStatusEnum.StatStarted;
}
else
{
//make sure the reference to process manager is null, in case somebody is messing with it
processManager = null;
//no need to register with process manager. but connect to peer process (if given)
if (!connect_to_peer_process())
{
//failed. but not a fatal error
logger.warn( "Couldn't connect to peer process!" );
}
//no need to wait for runtime parameters from process manager. create default
//(1)operation mode: if running as primary, always start as control
// if running as standby, only run as control if peer is not available
if (isPrimary)
{
logger.info( "Running as primary. Will go to CONTROL." );
requestedState = CosProcessStatusEnum.StatRunningControl;
}
else if (peerProcess == null)
{
logger.info( "Running as standby but no peer. Will go to CONTROL." );
requestedState = CosProcessStatusEnum.StatRunningControl;
}
else
{
logger.info( "Running as standby with peer (primary). Will go to MONITOR." );
requestedState = CosProcessStatusEnum.StatRunningMonitor;
}
//(2)default log file to the current directory
if (logFile.length() == 0)
{
logFile = "./" + entityName + ".log";
LoggerManager.setLogFile(logFile);
}
//(3)call the child custom configure application
if (!configureApplication2())
{
logger.error("Can not configure child process.");
state = CosProcessStatusEnum.StatUnstarted;
return false;
}
//(4)set the configure flag right away
isConfigured = true;
}
//update status
changeStatus(CosProcessStatusEnum.StatStarted);
return true;
}
protected boolean parseArguments(String[] args)
{
//command line arguments
options = new org.apache.commons.cli.Options();
Option arg = new Option("e", "Entity name (required)");
arg.setRequired(true);
arg.setLongOpt("entity");
arg.setArgs(1);
arg.setArgName("entity-name");
options.addOption(arg);
arg = new Option("f", "Configuration file (optional)");
arg.setRequired(false);
arg.setLongOpt("config-file");
arg.setArgs(1);
arg.setArgName("file-name");
options.addOption(arg);
arg = new Option("p", "Process Id (optional)");
arg.setRequired(false);
arg.setLongOpt("pid");
arg.setArgs(1);
arg.setArgName("process-id");
options.addOption(arg);
arg = new Option("m", "Manager port (optional)");
arg.setRequired(false);
arg.setLongOpt("mgr-port");
arg.setArgs(1);
arg.setArgName("port-no");
options.addOption(arg);
arg = new Option("l", "Log file (optional)");
arg.setRequired(false);
arg.setLongOpt("logfile");
arg.setArgs(1);
arg.setArgName("log-file");
options.addOption(arg);
arg = new Option("d", "Working directory (optional)");
arg.setRequired(false);
arg.setLongOpt("working-dir");
arg.setArgs(1);
arg.setArgName("port-no");
options.addOption(arg);
arg = new Option("cp", "Corba port (optional)");
arg.setRequired(false);
arg.setLongOpt("corba-port");
arg.setArgs(1);
arg.setArgName("port-no");
options.addOption(arg);
arg = new Option("nm", "Run as stand-alone (optional)");
arg.setRequired(false);
arg.setLongOpt("nomgr");
arg.setArgs(0);
options.addOption(arg);
arg = new Option("pn", "Peer hostname (if running as stand-alone)");
arg.setRequired(false);
arg.setLongOpt("peer-name");
arg.setArgs(1);
arg.setArgName("host-name");
options.addOption(arg);
arg = new Option("pp", "Peer port number (if running as stand-alone)");
arg.setRequired(false);
arg.setLongOpt("peer-port");
arg.setArgs(1);
arg.setArgName("port-no");
options.addOption(arg);
arg = new Option("pm", "Run as primary server (if running as stand-alone)");
arg.setRequired(false);
arg.setLongOpt("primary");
arg.setArgs(0);
options.addOption(arg);
//parser
org.apache.commons.cli.Parser parser = new org.apache.commons.cli.GnuParser();
//parse command line arguments
org.apache.commons.cli.CommandLine cmd = null;
try
{
cmd = parser.parse(options, args, true);
}
catch(org.apache.commons.cli.ParseException pe)
{
logger.error("Can not parse arguments: " + pe.toString());
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( "Command line parameters:", options );
return false;
}
//get the value
//entity name
if (cmd.hasOption("e"))
{
entityName = cmd.getOptionValue("e");
props.put("tcg.runtime.entity", entityName);
}
//pid
if (cmd.hasOption("p"))
{
pid = __parseInt(cmd.getOptionValue("p"));
}
else
{
pid = Utilities.getPid();
}
props.put("tcg.runtime.pid", Long.toString(pid));
//config/properties file
if (cmd.hasOption("f"))
{
configFile = cmd.getOptionValue("f");
props.put("tcg.runtime.configfile", configFile);
}
//manager port
if (cmd.hasOption("m"))
{
managerPort = __parseInt(cmd.getOptionValue("m"));
props.put("tcg.runtime.managerport", Integer.toString(managerPort));
}
//log file
if (cmd.hasOption("l"))
{
//return null if the parameter is not set
logFile = cmd.getOptionValue("l");
props.put("tcg.runtime.logfile", logFile);
//instance.setLogFile(logfile);
logger.info("logfile = " + logFile);
}
//corba port
if (cmd.hasOption("cp"))
{
corbaPort = __parseInt(cmd.getOptionValue("cp"));
props.put("tcg.runtime.corbaport", Integer.toString(corbaPort));
}
//run as stand-alone flag
if (cmd.hasOption("nm"))
{
hasManager = false;
props.put("tcg.runtime.nomanager", "true");
}
//peer host name
if (cmd.hasOption("pn"))
{
peerName = cmd.getOptionValue("pn");
props.put("tcg.runtime.peername", peerName);
}
//peer port number
if (cmd.hasOption("pp"))
{
peerPort = __parseInt(cmd.getOptionValue("pp"));
props.put("tcg.runtime.peerport", Integer.toString(peerPort));
}
//run as primary flag
if (cmd.hasOption("pm"))
{
isPrimary = true;
props.put("tcg.runtime.primary", "true");
}
return true;
}
//Load properties file and filter out unneeded/empty properties
//It is also try to get the system properties first before overriding it
// with properties from file, if any.
private Properties load_properties(String fileName, Properties props)
{
Properties retprops = new Properties();
//Get from the system properties first
Properties pt = System.getProperties();
for(Enumeration<?> enumeration = pt.propertyNames(); enumeration.hasMoreElements();)
{
String key = (String) enumeration.nextElement();
String value = pt.getProperty(key);
if ((key.startsWith("tcg."))
&& value!=null && retprops.getProperty(key)==null)
{
retprops.setProperty(key, value);
}
//special entry. orb
if (key.startsWith("org.omg.") && value!=null && retprops.getProperty(key)==null)
{
retprops.setProperty(key, value);
}
}
//open the property file.
pt = new Properties();
InputStream stream = ClassLoader.getSystemResourceAsStream(fileName);
if (stream == null)
{
logger.error("Couldn't find " + fileName + " in classpath.");
}
else
{
//load the properties file
try
{
pt.load(stream);
}
catch(IOException ioe)
{
logger.error("Fail to load " + fileName + ". Exception: " + ioe.getMessage());
}
finally
{
//close the stream
try
{
stream.close();
}
catch (IOException ioe)
{
//ignore
}
}
//override the system properties with properties from file
for(Enumeration<?> enumeration = pt.propertyNames(); enumeration.hasMoreElements();)
{
String key = (String) enumeration.nextElement();
String value = pt.getProperty(key);
if (value!=null)
{
retprops.setProperty(key, value);
}
}
} //if (stream == null) - else
//add in any runtime params
if (props != null)
{
//override any other properties if any
for(Enumeration<?> enumeration = props.propertyNames(); enumeration.hasMoreElements();)
{
String key = (String) enumeration.nextElement();
String value = props.getProperty(key);
if (value!=null)
{
retprops.setProperty(key, value);
}
}
}
return retprops;
}
private boolean register_with_process_manager()
{
//build the corbaloc ior
String corbaloc = "corbaloc::localhost:" + managerPort + "/ProcessManager";
//verbose
if (logger.isTraceEnabled())
logger.trace("corbaloc: " + corbaloc);
//reset the reference
processManager = null;
//get the reference
try
{
org.omg.CORBA.Object obj = CorbaManager.stringToObject(corbaloc);
processManager = ICosProcessManagerHelper.narrow(obj);
}
catch (Exception ex)
{
logger.warn("Can not build reference to process manager: " + corbaloc);
return false;
}
for (int i=0; i<CORBA_ERROR_THRESHOLD; i++)
{
try
{
processManager.cosRegisterManagedProcess(entityName, CosProcessTypeEnum.ProcThread,
this._this(), pid);
//succesful
return true;
}
catch ( Exception ex )
{
logger.error( "(" + i + ") Couldn't register with Process Manager: " + corbaloc );
//ignore
}
}
//reset the reference
processManager = null;
return false;
}
private boolean connect_to_peer_process()
{
//validation: peer info is not provided
if (peerName.length() == 0 && peerPort == 0)
{
return false;
}
//build the corbaloc ior
String hostname = peerName;
if (hostname.length() == 0)
{
hostname = "localhost";
}
int portno = peerPort;
if (portno == 0)
{
portno = corbaPort;
}
String corbaloc = "corbaloc::" + hostname + ":" + portno + "/" + entityName;
//verbose
if (logger.isTraceEnabled())
logger.trace("corbaloc: " + corbaloc);
//reset the reference
peerProcess = null;
//get the reference
try
{
org.omg.CORBA.Object obj = CorbaManager.stringToObject(corbaloc);
peerProcess = ICosManagedProcessHelper.narrow(obj);
}
catch (Exception ex)
{
logger.warn("Can not build reference to peer process: " + corbaloc);
return false;
}
for (int i=0; i<CORBA_ERROR_THRESHOLD; i++)
{
try
{
peerProcess.cosPoll();
return true;
}
catch ( Exception ex )
{
logger.error( "(" + i + ") Couldn't connect to peer process: " + corbaloc );
//ignore
}
}
//reset the reference
peerProcess = null;
return false;
}
private boolean configure_database_connection()
{
boolean isEncrypted = false;
if (0 == props.getProperty("tcg.db.encrypted", "").compareToIgnoreCase("true"))
{
isEncrypted = true;
}
//get the database type
DatabaseType dbType = null;
String dbTypeString = props.getProperty("tcg.db.type", "");
if (dbTypeString.equalsIgnoreCase("ORACLE"))
{
dbType = DatabaseType.ORACLE;
}
else if (dbTypeString.equalsIgnoreCase("MYSQL"))
{
dbType = DatabaseType.MYSQL;
}
else
{
dbType = DatabaseType.HSQLDB; //default
}
//configure primary database connection
String dbTnsname = props.getProperty("tcg.db.name", "");;
String dbUsername = props.getProperty("tcg.db.user", "");
String dbPassword = props.getProperty("tcg.db.password", "");
if (isEncrypted)
{
dbPassword = DatabaseManager.decrypt(dbUsername);
}
DatabaseManager.configure(dbType, dbTnsname, dbUsername, dbPassword);
return true;
}
protected boolean configureApplication()
{
logger.trace("configureApplication()");
//ignore database connection parameter from process manager.
//now, load it from properties file directly
// boolean isEncrypted = false;
// if (0 == props.getProperty("tcg.db.encrypted", "").compareToIgnoreCase("true"))
// {
// isEncrypted = true;
// }
//
// //configure primary database connection
// String dbTnsname = props.getProperty(PRIMARY_DB_TNSNAME_KEY.value, "");
// String dbUsername = props.getProperty(PRIMARY_DB_USERNAME_KEY.value, "");
// String dbPassword = props.getProperty(PRIMARY_DB_PASSWORD_KEY.value, "");
// String dbTypeString = props.getProperty(PRIMARY_DB_TYPE_KEY.value, "");
// //if any of the parameter is not given, try to use properties from config file.
// if (dbTnsname.length() == 0)
// {
// dbTnsname = props.getProperty("tcg.db.name", "");
// }
// if (dbUsername.length() == 0)
// {
// dbUsername = props.getProperty("tcg.db.user", "");
// }
// if (dbPassword.length() == 0)
// {
// dbPassword = props.getProperty("tcg.db.password", "");
// if (isEncrypted)
// {
// dbPassword = DatabaseManager.decrypt(dbUsername);
// }
// }
// //get the database type
// DatabaseType dbType = null;
// if (dbTypeString.length() == 0)
// {
// dbTypeString = props.getProperty("tcg.db.type", "");
// if (dbTypeString.equalsIgnoreCase("ORACLE"))
// {
// dbType = DatabaseType.ORACLE;
// }
// else if (dbTypeString.equalsIgnoreCase("MYSQL"))
// {
// dbType = DatabaseType.MYSQL;
// }
// else
// {
// dbType = DatabaseType.HSQLDB; //default
// }
// }
// DatabaseManager.configure(dbType, dbTnsname, dbUsername, dbPassword);
//run the custom configuration
if (!configureApplication2())
{
logger.trace("configureApplication() exit: FAILED");
return false;
}
isConfigured = true;
logger.trace("configureApplication() exit: SUCCESSFUL");
return true;
}
protected void changeStatus(CosProcessStatusEnum status)
{
this.state = status;
if (hasManager && processManager != null)
{
try {
processManager.cosProcessStatusChanged(entityName, status);
} catch (Exception ex) {
logger.warn("Couldn't update status: " + ex.toString());
}
}
else
{
//direct peer synchronization
if (status == CosProcessStatusEnum.StatRunningControl)
{
//start the peer notifier thread
peerNotifierThread_.start();
}
else if (status == CosProcessStatusEnum.StatGoingToMonitor
|| status == CosProcessStatusEnum.StatRunningMonitor)
{
//start peer poller thread, if it is not yet started
peerPollingThread_.start();
}
}
}
private boolean can_go_to_monitor()
{
boolean status = false;
if (hasManager && processManager != null)
{
try {
status = processManager.cosProcessGoingToMonitor(entityName);
} catch (Exception ex) {
logger.warn("Couldn't get permission to monitor: " + ex.toString());
}
}
else
{
//When running as stand-alone, the only time this is called is when we set
//requested state in initialize() or when there is external CORBA call.
//Either way we should just let it.
//NOTE: Another possible implementation is to only let it go to monitor
// if and only if the the peer is in CONTROL mode.
// This would guarantee that at least one process is in CONTROL.
// But this block a legitimate case when running a single instance of
// managed process (without peer) and we want to set it to monitor
// manually.
// It might also trigger back-and-forth switching between a pair of
// process. In fact it might prevent swithing altogether because the
// standby process can not switch to control unless the primary is in
// monitor (or dead) but the primary process can not go to monitor
// unless the other process (the standby process) is in control!
return true;
}
return status;
}
private boolean can_go_to_control()
{
boolean status = false;
//if we do not have manager, always allow
if (hasManager && processManager != null)
{
try {
status = processManager.cosProcessGoingToControl(entityName);
} catch (Exception ex) {
logger.warn("Couldn't get permission to control: " + ex.toString());
}
}
else
{
if (isPrimary)
{
//we are primary. always allow
return true;
}
else
{
//make sure we really try
if (peerProcess == null)
{
connect_to_peer_process();
}
if (peerProcess == null)
{
//peer is not running or not contactable
return true;
}
else
{
//peer is running. make sure peer is in monitor
try
{
peerProcess.cosPollMonitor();
status = true;
}
catch (Exception ex)
{
//ignore
}
} //if (peerProcess == null) - else
} //if (isPrimary) - else
}
return status;
}
protected void run()
{
//store ior into persistence storage
CorbaManager.persistIor(entityName, this._this());
logger.info("---- MAIN LOOP ----"); //ignore
//keep reference to the main thread
thread = Thread.currentThread();
//hopefully this prevent more than one main thread!
synchronized(mutex)
{
keepRunning = true;
//wait until the application is properly configured
while (!isConfigured && keepRunning)
{
try
{
logger.debug("Waiting for configuration..."); //ignore
Thread.sleep(1000);
}
catch (InterruptedException ie)
{
logger.info("Sleep interrupted."); //ignore
}
}
int counter = 0;
//main loop
while (keepRunning)
{
if (state == CosProcessStatusEnum.StatTerminating)
{
//this is a hack. should never get into here
keepRunning = false;
break;
}
//block unless notified by other thread
counter = 0;
while (state == requestedState)
{
logger.debug("Process " + entityName + " current state: " +
CorbaHelper.ProcessStateToString(state));
counter++;
if (counter == VERBOSE_STATE_DEBUGGING_COUNTER)
{
logger.debug("Process " + entityName + " current state: " +
CorbaHelper.ProcessStateToString(state));
counter = 0;
}
//TODO -> find out why wait() doesn't work!!!
//__wait(mutex, keepRunning);
try
{
Thread.sleep(1000);
}
catch (InterruptedException ie)
{
logger.info("Sleep interrupted.");
//ignore
}
if (!keepRunning) break;
}
//change state requested
CosProcessStatusEnum prevstate = CosProcessStatusEnum.StatUnstarted;
switch(requestedState.value())
{
case CosProcessStatusEnum._StatRunningMonitor:
logger.info("Process " + entityName + " is going to MONITOR");
if (!can_go_to_monitor())
{
logger.warn("Process " + entityName + " is not allowed to go to MONITOR");
requestedState = state;
break;
}
prevstate = state;
changeStatus(CosProcessStatusEnum.StatGoingToMonitor);
//if prev state is control. stop it. this is a switch
if (prevstate == CosProcessStatusEnum.StatRunningControl)
{
stopControl();
}
//run the monitor loop
if (!startMonitor())
{
//fail. try to go back to previous state
logger.info("Process " + entityName + " failed to switch to MONITOR. Returning to previous state: "
+ CorbaHelper.ProcessStateToString(prevstate));
requestedState = prevstate;
}
else
{
//notify process manager
changeStatus(CosProcessStatusEnum.StatRunningMonitor);
}
break;
case CosProcessStatusEnum._StatRunningControl:
logger.info("Process " + entityName + " is going to CONTROL");
if (!can_go_to_control())
{
logger.warn("Process " + entityName + " is not allowed to go to CONTROL");
requestedState = state;
break;
}
prevstate = state;
changeStatus(CosProcessStatusEnum.StatGoingToControl);
//if prev state is control. stop it. this is a switch
if (prevstate == CosProcessStatusEnum.StatRunningMonitor)
{
stopMonitor();
}
//run the control loop
if (!startControl())
{
//fail. try to go back to previous state
logger.info("Process " + entityName + " failed to switch to CONTROL. Returning to previous state: "
+ CorbaHelper.ProcessStateToString(prevstate));
requestedState = prevstate;
}
else
{
//notify process manager
changeStatus(CosProcessStatusEnum.StatRunningControl);
}
break;
case CosProcessStatusEnum._StatStopped:
prevstate = state;
state = CosProcessStatusEnum.StatTerminating;
//notify process manager
try
{
processManager.cosProcessTerminating(entityName,
CosTerminationCodeEnum.TermRequestedTerminate);
}
catch (Exception ex)
{
logger.warn("Couldn't update termination status: " + ex.getMessage());
}
//stop current loop
if (prevstate == CosProcessStatusEnum.StatRunningControl)
{
stopControl();
}
else if (prevstate == CosProcessStatusEnum.StatRunningMonitor)
{
stopMonitor();
}
//quit the main loop
keepRunning = false;
break;
default:
//nothing to do here except reset the flag and notify process manager
changeStatus(requestedState);
}
}
}
//update status
//shutting down Orb will stop all corba services.
//this includes the ability to make a corba call.
//thus, we have to update the status before we actually shutdown Orb
changeStatus(CosProcessStatusEnum.StatStopped);
//remove ior from persistence storage
CorbaManager.removeIor(entityName);
CorbaManager.shutdown();
CorbaManager.cleanup();
}
public String getEntityName()
{
return entityName;
}
public void setLogger(Logger logger)
{
this.logger = logger;
this.loggerWriter = new LoggerWriter(logger);
}
public Logger getLogger()
{
return logger;
}
public LoggerWriter getLoggerWriter()
{
return loggerWriter;
}
public CosProcessStatusEnum getStatus()
{
return state;
}
public void printConfiguration()
{
//Print out all configuration as visual feedback (runtime)
Enumeration<Object> keysEnum = props.keys();
//convert it into a string vector so that I can sort it
Vector<String> keyList = new Vector<String>();
while(keysEnum.hasMoreElements())
{
keyList.add((String)keysEnum.nextElement());
}
//sort it
Collections.sort(keyList);
//print out
for(int i=0; i<keyList.size(); i++)
{
String key = keyList.get(i);
String value = props.getProperty(key);
//except if it contain the word "password" in its name
if (key.toLowerCase().contains("password"))
{
logger.info("Property " + key + " = " + value.replaceAll(".", "*"));
}
else
{
logger.info("Property " + key + " = " + value);
}
} //for each key
}
public int getCorbaPort()
{
return corbaPort;
}
public String getWorkingDirectory()
{
return workingDir;
}
public boolean isManagedAgent()
{
return hasManager;
}
public int getProcessManagerPort()
{
return managerPort;
}
public final Properties getRuntimeProperties()
{
return props;
}
/**
* Get peer host name.
* @return the peer host name.
*/
public String getPeerName()
{
return peerName;
}
/**
* Get peer port number.
* @return the peer port number
*/
public int getPeerPort()
{
return peerPort;
}
// @SuppressWarnings("unused")
// protected void __wait(Object obj, boolean flag)
// {
// if (obj == null || !flag) return;
// try {
// obj.wait();
// } catch (InterruptedException ie) {
// //ignore
// logger.warn("InterruptedException: " + ie.getMessage());
// }
// }
//
// protected void __notify(Object obj)
// {
// if (obj == null) return;
// try
// {
// obj.notifyAll();
// }
// catch (IllegalMonitorStateException ex)
// {
// logger.trace("Exception: " + ex.toString());
// }
// }
protected int __parseInt(String text)
{
int retval = 0;
try
{
retval = Integer.parseInt(text);
}
catch (NumberFormatException ne)
{
logger.trace("Exception: " + ne.toString());
}
return retval;
}
/*
* CORBA Subroutines
*/
//ICosManagedProcess
public void cosTerminate()
{
stop();
}
public void cosSetOperationMode(CosOperationModeEnum mode)
{
logger.info(entityName + ": received new operation mode " +
CorbaHelper.OperationModeToString(mode));
//ignore when terminating. client should never send conflicting request. but who knows?
if (state == CosProcessStatusEnum.StatTerminating)
return;
//translate the operation mode into a process state
if (mode == CosOperationModeEnum.OperControl)
{
if (state == CosProcessStatusEnum.StatGoingToControl ||
state == CosProcessStatusEnum.StatRunningControl)
{
logger.info("Already in CONTROL mode.");
}
else
{
requestedState = CosProcessStatusEnum.StatRunningControl;
}
}
else if (mode == CosOperationModeEnum.OperMonitor)
{
if (state == CosProcessStatusEnum.StatGoingToMonitor ||
state == CosProcessStatusEnum.StatRunningMonitor)
{
logger.info("Already in MONITOR mode.");
}
else
{
requestedState = CosProcessStatusEnum.StatRunningMonitor;
}
}
//trigger the main thread. only expect ONE main thread!!!
if (thread != null)
{
thread.interrupt();
}
}
public CosProcessStatusEnum cosGetStatus()
{
return state;
}
public CosOperationModeEnum cosGetOperationMode()
{
CosOperationModeEnum retval = null;
if(state == CosProcessStatusEnum.StatRunningControl)
{
retval = CosOperationModeEnum.OperControl;
}
else if (state == CosProcessStatusEnum.StatRunningMonitor)
{
retval = CosOperationModeEnum.OperMonitor;
}
else
{
retval = CosOperationModeEnum.OperNotApplicable;
}
return retval;
}
public void cosSetParams(CosRunParamStruct[] paramSeq)
{
boolean reconfigure = false;
for (int i=0; i<paramSeq.length; i++)
{
logger.info(entityName + ": param " + paramSeq[i].name + " = " + paramSeq[i].value);
props.setProperty(paramSeq[i].name, paramSeq[i].value);
if (paramSeq[i].name.equalsIgnoreCase(LOG_LEVEL_KEY.value))
{
logger.info("Received a new log level: " + paramSeq[i].value);
LoggerManager.setLogLevel(CorbaHelper.LogLevelStringToIntLogLevel(paramSeq[i].value));
}
else if (paramSeq[i].name.equalsIgnoreCase(LOG_FILE_KEY.value))
{
logger.info("Received a new log file: " + paramSeq[i].value);
LoggerManager.setLogFile(paramSeq[i].value);
}
//from now on, ignore database parameters from process manager.
//instead, load it from properties file!
// else if (paramSeq[i].name.equalsIgnoreCase(CENTRAL_DB_TNSNAME_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(CENTRAL_DB_USERNAME_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(CENTRAL_DB_PASSWORD_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(PRIMARY_DB_TNSNAME_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(PRIMARY_DB_PASSWORD_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(PRIMARY_DB_USERNAME_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(STANDBY_DB_TNSNAME_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(STANDBY_DB_USERNAME_KEY.value)
// || paramSeq[i].name.equalsIgnoreCase(STANDBY_DB_PASSWORD_KEY.value))
// {
// reconfigure = true;
// }
reconfigure = true;
}
//configure the application only if it is not yet configured.
//prevent reconfiguration!!!
if (reconfigure)
{
if (isConfigured)
{
logger.warn("Already configured. Will not reconfigure!");
}
else
{
//this is actually very dangerous.
//chances are, this will take very long to complete.
//this cosSetParam might then timeout throwing CORBA::TIMEOUT exception
configureApplication();
}
} //if (reconfigure)
} //cosSetParams()
public CosRunParamStruct[] cosGetParams()
{
CosRunParamStruct[] retval = new CosRunParamStruct[props.size()];
Enumeration<Object> iter = props.keys();
String key = null, value = null;
int i = 0;
while (iter.hasMoreElements())
{
key = (String) iter.nextElement();
value = (String) props.getProperty(key);
if (value != null)
{
retval[i].name = key;
retval[i].value = value;
}
i++;
}
return retval;
}
public void cosSetBaseWeightage(short inWeightage)
{
this.weightage = inWeightage;
}
public short cosGetWeightage()
{
return this.weightage;
}
public String cosGetStatusString()
{
//list down all jobs and trigger
return getStatusString();
}
public void cosSetLogLevel(CosLogLevelEnum loglevel)
{
logger.info("New log level: " + CorbaHelper.LogLevelToString(loglevel));
Level level = CorbaHelper.LogLevelToIntLogLevel(loglevel);
//set log level globally
LoggerManager.setLogLevel(level);
}
public void cosSetLogLevelDetail(String loggername, CosLogLevelEnum loglevel)
{
logger.info("Log level for logger " + loggername + " changed. New log level: " +
CorbaHelper.LogLevelToString(loglevel));
Level level = CorbaHelper.LogLevelToIntLogLevel(loglevel);
LoggerManager.setLogLevelDetail(loggername, level);
}
//ICosMonitoredThread
public long cosGetProcessId()
{
if (pid == 0)
{
pid = (int) Native.getpid();
}
return pid;
}
//override this to provide more specific process type
public CosProcessTypeEnum cosGetProcessType()
{
return CosProcessTypeEnum.ProcThread;
}
public void cosPoll() throws CosPollException
{
//PollException only need to be called when we need to inform caller/poller
//that we have some internal problem eventhough we are still running.
return;
}
public void cosPollControl() throws CosPollException, CosPollNotInModeException
{
if (state != CosProcessStatusEnum.StatRunningControl)
{
throw new CosPollNotInModeException();
}
return;
}
public void cosPollMonitor() throws CosPollException, CosPollNotInModeException
{
if (state != CosProcessStatusEnum.StatRunningMonitor)
{
throw new CosPollNotInModeException();
}
return;
}
/**
* Thread for polling the primary peer (when running as stand-alone)
* @author Yoga
*/
class PrimaryPollerThread implements Runnable
{
private Thread thread_ = null;
private boolean keepThreadRunning_ = false;
/**
* Start the thread.
*/
public void start()
{
//avoid running more than once
if (thread_ != null && thread_.isAlive())
{
return;
}
//create a new plan master thread
thread_ = new Thread(this);
//finally, start the thread and run the plan master
thread_.start();
}
/**
* Stop the current thread.
*
* @param millis - wait for termination, in msec.
*/
public void stop(long millis)
{
if (thread_ == null)
{
return;
}
//stop the listen thread
keepThreadRunning_ = false;
try
{
thread_.interrupt();
if (millis > 0)
thread_.join(millis);
}
catch (InterruptedException ie)
{
//ignore
}
thread_ = null;
}
/**
* Polling loop
*/
public void run()
{
// TODO Auto-generated method stub
//just to make sure that we are very careful
if (peerProcess == null)
{
connect_to_peer_process();
}
//if we still can not get valid reference to peer process, just quit
if (peerProcess == null)
{
return;
}
keepThreadRunning_ = true;
//keep polling the primary.
boolean status = true;
while (keepThreadRunning_ && status)
{
status = false;
for (int i=0; i<CORBA_ERROR_THRESHOLD && !status; i++)
{
try
{
peerProcess.cosPollControl();
status = true;
}
catch (Exception ex)
{
//fail to confirm that primary is in control.
}
} //end for-loop
if (status)
{
//sleep for a while
try
{
Thread.sleep(peerSyncRate);
}
catch (InterruptedException ie)
{
//ignore
}
}
} //end while(status)
keepThreadRunning_ = false;
//primary server switch to monitor or it has terminated.
//switch ourselves to control
requestedState = CosProcessStatusEnum.StatRunningControl;
//reset the reference to peer
peerProcess = null;
}
}
/**
* Thread for notifying the standby peer (when running as stand-alone)
* @author Yoga
*/
class StandbyNotifierThread implements Runnable
{
private Thread thread_ = null;
private boolean keepThreadRunning_ = false;
/**
* Start the thread.
*/
public void start()
{
//avoid running more than once
if (thread_ != null && thread_.isAlive())
{
return;
}
//create a new plan master thread
thread_ = new Thread(this);
//finally, start the thread and run the plan master
thread_.start();
}
/**
* Stop the current thread.
*
* @param millis - wait for termination, in msec.
*/
public void stop(long millis)
{
if (thread_ == null)
{
return;
}
//stop the listen thread
keepThreadRunning_ = false;
try
{
thread_.interrupt();
if (millis > 0)
thread_.join(millis);
}
catch (InterruptedException ie)
{
//ignore
}
thread_ = null;
}
/**
* Notification loop
*/
public void run()
{
//just to make sure that we are very careful
if (peerProcess == null)
{
connect_to_peer_process();
}
//if we still can not get valid reference to peer process, just quit
if (peerProcess == null)
{
return;
}
keepThreadRunning_ = true;
//keep polling the primary.
boolean status = true;
while (keepThreadRunning_ && status)
{
status = false;
for (int i=0; i<CORBA_ERROR_THRESHOLD && !status; i++)
{
try
{
peerProcess.cosSetOperationMode(CosOperationModeEnum.OperControl);
status = true;
}
catch (Exception ex)
{
//fail to notify the standby that we are in control.
}
} //end for-loop
if (status)
{
//sleep for a while
try
{
Thread.sleep(peerSyncRate);
}
catch (InterruptedException ie)
{
//ignore
}
}
} //end while(status)
keepThreadRunning_ = false;
//either we are terminating or the peer process has terminated.
//in any case, reset the peer reference
peerProcess = null;
}
}
}