Package org.openeai.afa

Source Code of org.openeai.afa.ScheduledApp$ShutdownHook

/*******************************************************************************
$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 sConfigthrows 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.");
    }
  }
}
TOP

Related Classes of org.openeai.afa.ScheduledApp$ShutdownHook

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.