/**
* SchedulingAgent.java
*
* Run a list of scheduled jobs configured in the database.
*
* Scheduling agent is running as a managed process under the Process Manager.
* As such it extends the scada common's ManagedProcess class.
*
* Scheduling agent is supposed to be executed as an agent by Process Manager from
* command line. As such it has main() function.
*
* @author Wahyu Yoga Pratama (wahyu@stee.stengg.com)
*
* @created Feb 08, 2006
* @version $$
*
* HISTORY:
* - 2008/02/06 Created.
*
* TODO:
* -
*/
package tcg.scheduling;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Properties;
import java.io.IOException;
import java.io.InputStream;
import java.lang.StringBuilder;
//import oracle.jdbc.pool.OracleConnectionCacheImpl;
//import oracle.jdbc.pool.OracleConnectionPoolDataSource;
import org.apache.commons.cli.HelpFormatter;
import org.apache.log4j.Logger;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import tcg.common.DatabaseManager;
import tcg.common.LoggerManager;
import tcg.common.Utilities;
import tcg.syscontrol.ManagedProcess;
public class SchedulingAgent extends ManagedProcess
{
public static final String VERSION = "01.01 (20091112)";
public static final String SCHEDULER_ARG_STRING = "Parent";
public static final String ARGUMENT_ARG_STRING = "Arguments";
public static final String TASK_ARG_STRING = "Task";
public static final long EXECUTION_DELAY_MILLIS = 30000;
public static final String DEF_CRONTABLE = "SCHED_CRONMASTER";
private static final String DEF_CRONDOMAIN = "TCG";
// Number of threads
private static final String DEF_THREADCOUNT = "2";
// Simple Trigger
public static final String DEF_STARTTIME = "";
public static final String DEF_ENDTIME = "";
public static final String DEF_REPEATCOUNT = "" + SimpleTrigger.REPEAT_INDEFINITELY;
public static final String DEF_INTERVAL = "5000";
// Cron Trigger
public static final String DEF_CRONSTRING = "";
private static Logger logger_ = LoggerManager.getLogger(SchedulingAgent.class.toString());
private Scheduler scheduler_ = null;
private String domain_ = DEF_CRONDOMAIN;
//the calendar instance
private Calendar calendar_ = new GregorianCalendar();
//the in-process thread
private Thread thread_ = null;
/**
* Main entry for in-process execution. This must run on its thread!
*/
public void execute(String args)
{
String[] params = args.split(" ");
thread_ = new Thread(new InProcessExecution(params));
thread_.start();
}
/**
* Main entry for command line execution.
* @param args - list of command line arguments
*/
public static void main(String[] args)
{
//if arguments has "--version", print version number and quit
String logfile = "";
for (int i=0; i<args.length; i++)
{
if (args[i].equalsIgnoreCase("--version") || args[i].equalsIgnoreCase("-V")) {
printVersion();
return;
} else if (args[i].equalsIgnoreCase("--help") || args[i].equalsIgnoreCase("-h")) {
printUsage();
return;
} else if (args[i].equalsIgnoreCase("--logfile") || args[i].equalsIgnoreCase("-l")) {
if (++i<args.length) logfile = args[i];
}
}
//create the Scheduling Agent instance
SchedulingAgent instance = new SchedulingAgent();
//reset logging
if (logfile.length() > 0)
{
LoggerManager.setLogFile(logfile);
}
instance.setLogger(logger_);
logger_.info("---- Scheduling Agent starting ----");
if (!instance.initialize(args))
{
logger_.error("Failed to initialize. Quitting!");
logger_.info("---- Scheduling Agent has shut down ----");
return;
}
//Print out all configuration as visual feedback (runtime)
instance.printConfiguration();
//run the agent
logger_.info("---- Scheduling Agent is running ----");
//instance.xxx_run();
instance.run();
//shutting down
logger_.info("---- Scheduling Agent is shutting down ----");
//clean up
//Nothing
//done
logger_.info("---- Scheduling Agent has shut down ----");
}
private static void printVersion()
{
System.out.println("Scheduling Agent Version " + VERSION);
}
protected static final void printUsage()
{
System.out.println("Scheduling Agent Version " + VERSION);
System.out.println("");
ManagedProcess.printUsage();
System.out.println("");
System.out.println("Scheduling Agent Additional Command Line Parameters: ");
System.out.println(" -d | --domain <domain-name> Scheduling agent domain");
System.out.println("");
System.out.println("Event Agent Additional Java Properties (Configuration file or System properties): ");
System.out.println(" tcg.sched.domain Default scheduler domain");
System.out.println(" tcg.sched.thread No of worker thread for scheduler");
System.out.println(" tcg.sched.configfile Scheduler properties files");
System.out.println("");
System.out.println("Command line parameters will override java properties and configuration file value.");
System.out.println("");
System.out.println("Component Library Information:");
System.out.println("\t - Quartz Scheduler Ver. 1.6.6");
System.out.println("\t - Log4J Ver. 1.2.12");
System.out.println("\t - Oracle JDBC Ver. 10.2");
System.out.println("\t - JacORB Ver. 2.3");
System.out.println("\t - Avalon Framework Ver. 4.1.5");
System.out.println("");
System.out.println("Other Component Information:");
System.out.println("");
}
@Override
public String getStatusString()
{
StringBuilder strbuffer = new StringBuilder();
//list down all jobs and trigger
try {
String[] jobGroups;
String[] jobsInGroup;
int i;
int j;
jobGroups = scheduler_.getJobGroupNames();
for (i = 0; i < jobGroups.length; i++) {
strbuffer.append("Group: " + jobGroups[i] + " contains the following jobs\n");
jobsInGroup = scheduler_.getJobNames(jobGroups[i]);
for (j = 0; j < jobsInGroup.length; j++) {
strbuffer.append("- " + jobsInGroup[j] + "\n");
logger.info("- " + jobsInGroup[j]);
}
}
String[] triggerGroups;
String[] triggersInGroup;
triggerGroups = scheduler_.getTriggerGroupNames();
for (i = 0; i < triggerGroups.length; i++) {
strbuffer.append("Group: " + triggerGroups[i] + " contains the following triggers\n");
triggersInGroup = scheduler_.getTriggerNames(triggerGroups[i]);
for (j = 0; j < triggersInGroup.length; j++) {
strbuffer.append("- " + triggersInGroup[j] + "\n");
}
}
} catch (Exception ex) {
//Ignore
strbuffer.append(ex.toString());
}
return strbuffer.toString();
}
@Override
public boolean initialize2(String[] args, Properties props)
{
//Specific configuration setting
//get the scheduling agent domain
options.addOption("d", "domain", true, "Scheduling Agent Domain");
//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.getMessage());
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( "Parameters:", options );
return false;
}
//get the value: agent port
if (cmd.hasOption("d"))
{
domain_ = cmd.getOptionValue("d");
props.put("tcg.runtime.domain", domain_);
}
//if the domain name is not given in the command line, use default
if (domain_ == null || domain_.length() == 0)
{
domain_ = props.getProperty("tcg.sched.domain", DEF_CRONDOMAIN);
}
return true;
}
@Override
public boolean configureApplication2()
{
//nothing to do since database configuration is already performed in
//ManagedProcess class
return true;
}
@Override
/**
* The following properties is required:
* - ste.sched.configFile : Quartz scheduler configuration file
* - ste.sched.thread : No of threads in the scheduler thread pool
*/
public boolean startControl()
{
boolean status = false;
//Start the scheduler
Connection conn = DatabaseManager.getConnection();
if (conn == null)
{
return false;
}
StdSchedulerFactory factory;
int errorCode = 0;
try
{
//Initialize scheduler
logger.info("Initializing Quartz java scheduler");
String quartzConfigFile = props.getProperty("tcg.sched.configfile", "");
String threadNo = props.getProperty("tcg.sched.thread", DEF_THREADCOUNT);
//load properties from file
Properties quartzProps = new Properties();
if (quartzConfigFile.length() >= 0)
{
InputStream in = Utilities.getInputStream(quartzConfigFile);
if (in != null)
{
quartzProps.load(in);
try
{
in.close();
}
catch(IOException ioe)
{
//ignore
}
}
}
//override properties with some of our setting
quartzProps.setProperty("org.quartz.threadPool.threadCount", threadNo);
String str = quartzProps.getProperty("org.quartz.threadPool.class");
if (str == null || str.length() == 0)
{
quartzProps.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
}
//create the scheduler
factory = new StdSchedulerFactory(quartzProps);
scheduler_ = factory.getScheduler();
//Schedule all the jobs
scheduleJobs(scheduler_, conn);
//Start the scheduler
logger.info("Starting Quartz java scheduler");
scheduler_.start();
//successful
status = true;
}
catch (SQLException sqle)
{
logger.error("Database access error: " + sqle.getMessage());
logger.error("--------------------------------------------\n");
sqle.printStackTrace(loggerWriter);
logger.error("--------------------------------------------\n");
errorCode = sqle.getErrorCode();
}
catch (SchedulerException se) {
logger.error("Scheduler initialization error: " + se.getMessage());
logger.error("--------------------------------------------\n");
se.printStackTrace(loggerWriter);
logger.error("--------------------------------------------\n");
}
catch (Exception ex) {
logger.error("Exception: " + ex.getMessage());
logger.error("--------------------------------------------\n");
ex.printStackTrace(loggerWriter);
logger.error("--------------------------------------------\n");
}
DatabaseManager.returnConnection(errorCode);
if (status)
{
logger.info("---- Scheduling Agent is running CONTROL ----");
}
return status;
}
@Override
public boolean startMonitor()
{
logger.info("---- Scheduling Agent is running MONITOR ----");
//in monitor mode, we just sit idly!
return true;
}
@Override
public boolean stopControl()
{
if (scheduler_ == null)
return false;
//Do I really need this one?
logger.info("Shutting down Quartz java scheduler");
try
{
scheduler_.shutdown();
scheduler_ = null;
}
catch (Exception e)
{
//ignore
logger.error("Exception: " + e.getMessage());
logger.error("--------------------------------------------\n");
e.printStackTrace(loggerWriter);
logger.error("--------------------------------------------\n");
}
return true;
}
@Override
public boolean stopMonitor()
{
//Since in monitor mode it sits idle, nothing need to be stopped
return true;
}
// @Override
// public void eventHandler(CosNotificationEventEnum event, String eventInfo) {
// //ignore all events
// }
/**
*
* @param Scheduler Quartz scheduler object
* @param Connection Database connectionn
* @throws SQLException
*
* Read all schedule jobs from database and insert it into the scheduler.
*
* This function expect a database table in the following structure
* Table CRONMASTER
* - TASK_ID : String. Unique task id
* - DOMAIN : String. Task domain.
* - JOB_CLASS : String. Java class that perform the job
* - ARGUMENTS : String. Arguments to the java class
* - TRIGGER_TYPE : String. Trigger type ('CronTrigger' or 'SimpleTrigger').
* - TRIGGER_CRON : String. Cron trigger's cron statement
* - TRIGGER_START : Date. Simple trigger's start time
* - TRIGGER_END : Date. Simple trigger's end type
* - TRIGGER_REPEAT : Number. Simple trigger's no of repeat
* - TRIGGER_INTERVAL : Number. Simple trigger's interval
* - ENABLED : Char. Enabled flag ('Y' or 'N')
* - TOUPDATE : Char. Update flage ('Y' or 'N') used by MasterCronJob to update job definition
*/
private void scheduleJobs(Scheduler sched, Connection conn) throws SQLException
{
String taskGroup = domain_;
Statement stmt = conn.createStatement();
String query = "select * from " + DEF_CRONTABLE + " " +
"where DOMAIN='" + domain_ + "' and ENABLED='Y'";
ResultSet rs = stmt.executeQuery(query);
String curTask;
String taskJob, taskTrigger, taskArgs;
boolean isSimpleTrigger;
String taskCron="";
Date taskStartTime=null, taskEndTime=null;
int taskRepeat=0, taskInterval=0;
Class<?> jobClass = null;
Date startTime = null;;
Date endTime = null;
Date curTime = new Date();
int repeat=0;
long interval=0;
Trigger trigger = null;
JobDetail jobDetail = null;
while (rs.next()) {
curTask = rs.getString("TASK_ID");
taskJob = rs.getString("JOB_CLASS");
taskArgs = rs.getString("ARGUMENTS");
if (taskArgs == null)
{
taskArgs = "";
}
taskTrigger = rs.getString("TRIGGER_TYPE");
//verbose
logger.info("Task " + curTask);
logger.info("Task " + curTask + " - Job Class = " + taskJob);
logger.info("Task " + curTask + " - Trigger Type = " + taskTrigger);
logger.info("Task " + curTask + " - Arguments = " + taskArgs);
//validation
if (taskJob.length() == 0 || taskTrigger.length() == 0)
{
logger.error("You must provide both Job class and Trigger type for task "
+ curTask + "!");
continue;
}
//get the trigger setting
if (taskTrigger.compareToIgnoreCase("CronTrigger") == 0)
{
taskCron = rs.getString("TRIGGER_CRON");
if (null == taskCron) {
taskCron = DEF_CRONSTRING;
} else {
taskCron = taskCron.trim();
}
isSimpleTrigger = false;
//verbose
logger.info("Task " + curTask + " - Cron Value = " + taskCron);
}
else if (taskTrigger.compareToIgnoreCase("SimpleTrigger") == 0)
{
taskStartTime = rs.getDate("TRIGGER_START");
if (taskStartTime == null)
{
taskStartTime = new Date();
}
taskEndTime = rs.getDate("TRIGGER_END");
taskRepeat = rs.getInt("TRIGGER_REPEAT");
taskInterval = rs.getInt("TRIGGER_INTERVAL");
isSimpleTrigger = true;
//verbose
logger.info("Task " + curTask + " - Start Time = " + taskStartTime);
logger.info("Task " + curTask + " - End Time = " + taskEndTime);
logger.info("Task " + curTask + " - Repeat Count = " + taskRepeat);
logger.info("Task " + curTask + " - Interval = " + taskInterval);
if (taskEndTime != null &&
(curTime.after(taskEndTime) || !taskStartTime.before(taskEndTime)))
{
logger.warn("Task " + curTask + " is OBSOLETE! Ignored.");
continue;
}
}
else
{
logger.error("Invalid trigger type ("+ taskTrigger + ") for task " + curTask + "!");
continue;
}
//interpret parameters
try
{
jobClass = Class.forName(taskJob);
if (jobClass == null)
{
logger.error("Invalid job class (" + taskJob + ") for task " + curTask + "!");
continue;
}
}
catch (ClassNotFoundException e)
{
logger.error("Invalid job class (" + taskJob + ") for task " + curTask + "!");
continue;
}
catch (NoClassDefFoundError e)
{
//NOTE: NoClassDefFoundError does not seem to extend Exception
logger.error("Can not load class " + taskJob + ". Exception: " + e.toString());
continue;
}
catch (Exception e)
{
logger.error("Can not load class " + taskJob + ". Exception: " + e.toString());
continue;
}
if (isSimpleTrigger)
{
//delay the execution until the agent is properly started
startTime = new Date();
startTime.setTime(startTime.getTime()+EXECUTION_DELAY_MILLIS);
if (taskStartTime != null && taskStartTime.after(startTime)) {
startTime = taskStartTime;
}
endTime = taskEndTime;
if (taskRepeat < 0) {
repeat = SimpleTrigger.REPEAT_INDEFINITELY;
} else {
repeat = taskRepeat;
}
if (taskInterval < 0) {
interval = 0;
} else {
interval = taskInterval;
}
}
//create trigger
logger.info("Task " + curTask + " - Creating trigger...");
trigger = null;
if (isSimpleTrigger) {
try {
trigger = new SimpleTrigger(curTask + "_Trigger", null, startTime, endTime, repeat, interval);
} catch (Exception e) {
logger.error("Cannot create trigger for task " + curTask + "!");
logger.error("Exception: "+e.getMessage());
continue;
}
} else {
try {
trigger = new CronTrigger(curTask + "_Trigger", null, taskCron);
} catch (ParseException e) {
logger.error("Invalid cron epression (" + taskCron + ") for task " + curTask + "!");
continue;
}
}
trigger.getJobDataMap().put(SCHEDULER_ARG_STRING, this);
trigger.getJobDataMap().put(TASK_ARG_STRING, curTask);
trigger.getJobDataMap().put(ARGUMENT_ARG_STRING,taskArgs);
trigger.setGroup(taskGroup);
//create job
logger.info("Task " + curTask + " - Creating job...");
jobDetail = null;
try {
jobDetail = new JobDetail(curTask, null, jobClass);
} catch (Exception e) {
logger.error("Cannot create job for task " + curTask + "!");
logger.error("Exception: "+e.getMessage());
continue;
}
jobDetail.setGroup(taskGroup);
//schedulling the job/trigger
logger.info("Task " + curTask + " - Schedulling the job...");
try {
sched.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
logger.error("Cannot schedule job for task " + curTask + "!");
logger.error("Exception: "+e.getMessage());
continue;
}
}
rs.close();
//update status
stmt.execute("update " + DEF_CRONTABLE + " set TOUPDATE='N' where DOMAIN='"
+ domain_ + "'");
stmt.close();
}
public String getDomain()
{
return domain_;
}
public Calendar getCalendar()
{
return calendar_;
}
/**
* Thread for running this agent as in-process thread.
* @author Yoga
*/
class InProcessExecution implements Runnable
{
private String[] args_ = null;
/**
* Ctor. Provide the command line argument
* @param args - command line argument
*/
public InProcessExecution(String[] args)
{
args_ = args;
}
/**
* Main execution thread
*/
public void run()
{
//just run the static main() in its own thread
SchedulingAgent.main(args_);
}
}
}