/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/source/org/openeai/afa/ScheduledApp.java,v $
$Revision: 1.18 $
*******************************************************************************/
/**********************************************************************
This file is part of the OpenEAI Application Foundation or
OpenEAI Message Object API created by Tod Jackson
(tod@openeai.org) and Steve Wheat (steve@openeai.org) at
the University of Illinois Urbana-Champaign.
Copyright (C) 2002 The OpenEAI Software Foundation
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For specific licensing details and examples of how this software
can be used to build commercial integration software or to implement
integrations for your enterprise, visit http://www.OpenEai.org/licensing.
*/
package org.openeai.afa;
// General
import java.util.*;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import org.openeai.*;
import org.openeai.threadpool.*;
import org.openeai.dbpool.*;
import org.openeai.config.*;
/**
* This component is used to wrap ScheduledApplications. It is similar to our
* PointToPointConsumer and PubSubConsumer messaging components. However, instead
* of consuming messages and executing commands based on the message it consumed,
* it 'sleeps' for a specified amount of time then wakes up and checks the Schedules
* it contains to see if the commands they manage should be executed. If so, it
* calls the execute method on each one of the commands associated to the Schedule
* it's currently checking.
* <P>
* @author Tod Jackson (tod@openeai.org)
* @author Steve Wheat (steve@openeai.org)
* @version 3.0 - 28 January 2003
*/
public class ScheduledApp extends OpenEaiObject {
private HashMap m_schedules = new HashMap();
private ThreadPool m_threadPool = null;
private int m_scheduleCheckInterval = 5000; // Default: 5 seconds
private boolean m_stayAlive = true;
private String m_type = "";
private Thread m_mainThread = null;
private static String APPLICATION = "application";
private static String DAEMON = "daemon";
private static String TRIGGERED = "triggered";
private final static String NEW_LINE = System.getProperty("line.separator");
/**
* Constructor
*/
public ScheduledApp(ScheduledAppConfig sConfig) throws EnterpriseConfigurationObjectException {
setAppName(sConfig.getAppName());
setType(sConfig.getProperties().getProperty("type", DAEMON));
setScheduleCheckInterval(Integer.parseInt(sConfig.getProperties().getProperty("scheduleCheckInterval","5000")));
Iterator keys = sConfig.getScheduleConfigs().keySet().iterator();
while (keys.hasNext()) {
String name = (String)keys.next();
logger.info("Adding schedule: " + name);
Schedule aSchedule = null;
try {
aSchedule = new Schedule((ScheduleConfig)sConfig.getScheduleConfigs().get(name));
}
catch (Exception e) {
logger.fatal(e.getMessage(), e);
throw new EnterpriseConfigurationObjectException(e.getMessage(), e);
}
// retrieve the ScheduleIdStore from the ScheduledAppConfig object.
ScheduleIdStore store = sConfig.getScheduleIdStore();
if (store != null) {
logger.info("Schedule '" + name + "' will be using the '" + store.getClass().getName() + " ScheduleId Repository.");
aSchedule.setScheduleIdStore(store);
}
else {
// default to the FileScheduleIdStore
FileScheduleIdStore sidstore = new FileScheduleIdStore();
// if the 'ScheduleIdPath' property doesn't exist, the ScheduleIdStore will
// default it to 'ScheduleIds'
sidstore.setPath(sConfig.getProperties().getProperty("ScheduleIdPath",null));
try {
sidstore.load();
}
catch (Exception e) {
logger.fatal("Exception loading the SchedulIdStore. Exception: " + e.getMessage(), e);
}
aSchedule.setScheduleIdStore(sidstore);
}
addSchedule(name, aSchedule);
}
setThreadPool(new ThreadPoolImpl(sConfig.getThreadPoolConfig()));
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
ScheduledAppThread saThread = new ScheduledAppThread();
m_mainThread = new Thread(saThread);
m_mainThread.start();
}
private void setStayAlive(boolean alive) {
m_stayAlive = alive;
}
private boolean stayAlive() {
return m_stayAlive;
}
public void stop() {
Iterator it = m_schedules.keySet().iterator();
while (it.hasNext()) {
String key = (String)it.next();
Schedule s = (Schedule)m_schedules.get(key);
try {
logger.info("Shutting down Schedule '" + key + "'");
s.stop();
}
catch (Exception e) {
logger.warn("Error shutting down Schedule '" + key + "'", e);
}
}
// m_mainThread.interrupt();
// m_mainThread.destroy();
setStayAlive(false);
logger.info("All Schedules have been stopped.");
}
/**
* Returns the list of Schedules managed by this ScheduledApp
*
* @return HashMap Schedule objects managed by this ScheduledApp
*/
public HashMap getSchedules() {
return m_schedules;
}
/**
* Adds an individual Schedule to the list of schedules managed by this ScheduledApp.
* This is called during configuration of this object based on information found
* in its config document.
*
* @param name String the Schedule name
* @param schedule Schedule the Schedule object
*/
public void addSchedule(String name, Schedule schedule) {
if (schedule != null) {
logger.info("schedulename is: " + schedule.getName());
}
else {
logger.info("schedule is null!");
}
m_schedules.put(name, schedule);
}
/**
* Returns an individual Schedule from the list of schedules managed by this ScheduledApp.
*
* @param name String the Schedule name
* @return Schedule the Schedule object associated to the name passed in.
*/
public Schedule getSchedule(String name) {
return (Schedule)m_schedules.get(name);
}
private void setSchedules(HashMap schedules) {
m_schedules = schedules;
}
/**
* Sets the ThreadPool object associated to this ScheduledApp. The ThreadPool is
* is used to execute the commands associated to the Schedule.
*
* @param tPool ThreadPool
*/
public void setThreadPool(ThreadPool tPool) {
m_threadPool = tPool;
}
private ThreadPool getThreadPool() {
return m_threadPool;
}
/*
public void setDbConnectionPool(EnterpriseConnectionPool dbPool) {
m_connPool = dbPool;
}
public EnterpriseConnectionPool getDbConnectionPool() {
return m_connPool;
}
*/
private void setScheduleCheckInterval(int interval) {
m_scheduleCheckInterval = interval;
}
private int getScheduleCheckInterval() {
return m_scheduleCheckInterval;
}
private void setType(String type) {
m_type = type;
}
/**
* Returns the type of ScheduledApp that this is. Currently, those values can
* be 'application', 'triggered' or 'daemon'. These are set in the config document for this
* ScheduledApplication.
* <P>
* If the type is 'application' the ScheduledApp will retrieve
* all Schedules it manages and then call the execute methods on each command contained
* with those Schedules then exit.
* <P>
* If the type is 'triggered' the ScheduledApp will behave just like an 'application' except
* it will wait to be triggered before exiting. That is, some sort of 'kill' signal will
* have to be sent to the ScheduledApp. When that kill signal is received, it will exit.
* <P>
* If the type is 'daemon' the ScheduledApp will sleep for a specified period of time,
* then wake up and check each Schedule it manages and if that Schedule should be executed,
* it will call the execute method on each command managed by the Schedule.
*
* @return String the ScheduledApp type
*/
public String getType() {
return m_type;
}
/**
* This Thread is the main processing loop that the ScheduleApp goes into.
* Depending on the type of ScheduledApp we're running, it may sleep for a
* specified period of time and then execute all commands that should be executed
* at that time according to the Schedule they're associated with (type='daemon'),
* or if it's 'type' is 'application' it will simply iterate through all schedules
* and execute each command associated to that schedule before exiting.
*
* @author Tod Jackson
*
*/
private class ScheduledAppThread implements java.lang.Runnable {
public ScheduledAppThread() {
}
private void executeSchedule(Schedule schedule) {
if (stayAlive() == false) { return; }
String scheduleName = schedule.getName();
logger.info("[" + getAppName() + "] Executing command(s) for schedule " + scheduleName);
HashMap sCommands = schedule.getCommands();
Iterator it = sCommands.keySet().iterator();
while (it.hasNext()) {
String commandName = (String)it.next();
ScheduledCommand sCommand = (ScheduledCommand)sCommands.get(commandName);
if (getType().equals(APPLICATION) == false) {
if (getThreadPool() != null) {
boolean keepTrying = true;
while (keepTrying) {
try {
// getThreadPool().addJob(new ScheduledTransaction(scheduleName, commandName, sCommand));
getThreadPool().addJob(new ScheduledTransaction(schedule, commandName, sCommand));
keepTrying = false;
}
catch (ThreadPoolException e) {
logger.warn("ThreadPool is busy, sleeping and trying it again.");
try {
Thread.sleep(1000);
logger.info("Woke up, trying to add job to ThreadPool again...");
}
catch (Exception te) {
}
}
}
}
else {
// new ScheduledTransaction(scheduleName, commandName, sCommand).run();
new ScheduledTransaction(schedule, commandName, sCommand).run();
}
}
else {
// for 'applications' we don't want to use the thread pool. We just
// want to execute the schedules and then exit with the return code
// returned from the command (zero if they're all successful).
// If a command returns a non-zero return
// code, we will exit with that value and the other commands that may
// be associated to the current schedule will be ignored.
// If a command throws an exception, we'll also exit immediately with a non-zero return code.
try {
logger.info("Executing Command " + commandName + " in Schedule " +
scheduleName);
int rc = sCommand.execute();
logger.info("Done with " + scheduleName + "/" + commandName +
" Return Code: " + rc);
if (rc != 0) {
// exit with return code returned from command
logger.fatal("Scheduled command '" + commandName +
"' did not complete successfully (rc=" + rc +
"). Application must terminate.");
System.exit(rc);
}
}
catch (Exception e) {
logger.fatal("An exception occurred in the Scheduled command '" +
commandName + "'. Application must terminate.");
logger.fatal(e.getMessage(), e);
// exit with a non-zero return code
System.exit(-99);
}
}
}
}
public void run() {
// Give AppConfig a chance to log its "completed" message
try {
Thread.sleep(1000);
}
catch (Exception e) {
}
if (getType().equalsIgnoreCase(DAEMON)) {
// If it's a daemon process, we're going to sleep, wake up and execute then go back to sleep
while(stayAlive()) {
try {
//TODO: Make this message smarter.
//depending on the schedule check interval, make the message
//appropriate (Hours, Minutes, Seconds etc.).
logger.info("[" + getAppName() + "] Sleeping for " + getScheduleCheckInterval() / 1000 + " seconds.");
//TODO: Make it configurable as to whether or not we sleep initially
//or not.
Thread.sleep(getScheduleCheckInterval());
Iterator keys = getSchedules().keySet().iterator();
while (keys.hasNext()) {
String scheduleName = (String)keys.next();
Schedule schedule = (Schedule)getSchedule(scheduleName);
logger.info("[" + getAppName() + "] Checking schedule: " + schedule.getName());
if (schedule.isImmediate()) {
executeSchedule(schedule);
}
else {
if (schedule.shouldRun()) {
executeSchedule(schedule);
}
else {
logger.info("[" + getAppName() + "] schedule " + schedule.getName() + " doesn't need to run right now.");
}
}
}
}
catch (Exception e) {
logger.fatal("Exception occurred. Exception: " + e.getMessage(), e);
}
}
logger.info("ScheduledApp is being shutdown.");
}
else if (getType().equalsIgnoreCase(APPLICATION)) {
// If it's an 'application', we're just going to go through everything and execute all schedules.
// Then we're going to exit.
logger.info("[" + getAppName() + "] Executing all schedules.");
Iterator keys = getSchedules().keySet().iterator();
while (keys.hasNext()) {
String scheduleName = (String)keys.next();
Schedule schedule = (Schedule)getSchedule(scheduleName);
executeSchedule(schedule);
}
/*
logger.info("Waiting for threads to complete.");
while(getThreadPool().getJobsInProgress() > 0) {
try {
Thread.sleep(500);
}
catch (Exception e) {
}
}
logger.info("[" + getAppName() + "] All threads are complete. Now exiting.");
*/
logger.info("[" + getAppName() + "] All Schedules/Commands have completed successfully. Now exiting.");
System.exit(0);
}
else {
// If it's an 'triggered' application, we're just going to go through everything and execute all schedules.
// Then we're going to wait until someone kills the application.
logger.info("[" + getAppName() + "] Executing all schedules.");
Iterator keys = getSchedules().keySet().iterator();
while (keys.hasNext()) {
String scheduleName = (String)keys.next();
Schedule schedule = (Schedule)getSchedule(scheduleName);
executeSchedule(schedule);
}
logger.info("Waiting for threads to complete.");
while(getThreadPool().getJobsInProgress() > 0) {
try {
Thread.sleep(500);
}
catch (Exception e) {
}
}
logger.info("[" + getAppName() + "] All threads are complete. Waiting to be triggered to exit.");
}
}
}
/**
* This Thread will be used to execute the command(s) associated to a schedule.
* It can be added to the ScheduledApp's thread pool or ran in single threaded
* mode without a thread pool.
*
* @author Tod Jackson
*
*/
private class ScheduledTransaction implements java.lang.Runnable {
private ScheduledCommand m_command = null;
private String m_commandName = "";
private String m_scheduleName = "";
private Schedule m_schedule = null;
// public ScheduledTransaction(String scheduleName, String commandName, ScheduledCommand aCommand) {
public ScheduledTransaction(Schedule schedule, String commandName, ScheduledCommand aCommand) {
// m_scheduleName = scheduleName;
m_schedule = schedule;
m_scheduleName = schedule.getName();
m_commandName = commandName;
m_command = aCommand;
}
public void run() {
try {
logger.info("Executing Command " + m_commandName + " in Schedule " + m_scheduleName + " in ScheduledTransaction thread.");
int rc = m_command.execute();
logger.info("Done with " + m_scheduleName + "/" + m_commandName + " in ScheduledTransaction. Return Code: " + rc);
}
catch (Exception e) {
logger.fatal(e.getMessage(), e);
// send email to appropriate recipient(s) if configured to do so...
if (m_schedule.getMailService() != null) {
m_schedule.getMailService().setSubject("Exception occurred in the " + m_commandName + " command.");
StringBuffer sbuf = new StringBuffer();
sbuf.append("An exception occurred executing the " + m_commandName +
" command. This command is executed accoriding to the " + m_scheduleName +
" schedule. An exception stack trace follows." + NEW_LINE + NEW_LINE);
sbuf.append("Exception: " + NEW_LINE + NEW_LINE);
CharArrayWriter writer = new CharArrayWriter();
e.printStackTrace(new PrintWriter(writer));
sbuf.append(writer.toString());
m_schedule.getMailService().setMessageBody(sbuf.toString());
m_schedule.getMailService().sendMessage();
}
}
}
}
/**
* This Thread will be started when the scheduled app receives a shutdown signal from the os.
* It is established via the Runtime.getRuntime().addShutdownHook(new ShutdownHook());
* The purpose of this is to allow a "clean" shutdown of the schedule app
* without losing any transactions that might be in progress when shutdown occurs.
*
* @author Tod Jackson
*
*/
private class ShutdownHook extends Thread {
public void run() {
// Stop everything.
setStayAlive(false);
// we'll probably only want to wait for a maximum period of time
// until we go ahead and stop the consumer regardless of threads
// in progress??
if (getThreadPool() != null) {
while (getThreadPool().getJobsInProgress() > 0) {
try {
Thread.sleep(500);
}
catch (Exception e) {
}
}
}
logger.info(getName() + " - All threads are complete.");
logger.info(getName() + " - shutdown hook, scheduled app stopped, now exiting.");
}
}
}