2 Replies Latest reply on Jan 20, 2006 8:04 PM by Jason Long

    EJB3 Timer Scheduler

    Ian Terrell Newbie

      I'm using jBPM in an EJB3 application, and I didn't feel that the web tier was the proper place for the scheduler. I wrote a stateless session bean that uses EJB3 timers for scheduling instead. It's basically the same algorithm (oh, and same code :) that's in the servlet, just now instead of using threads directly it lets the application server handle the details of the timing. I took out the listeners for now.

      Obviously since it needs to be started on startup I have a servlet calling down to the start() method, but this fits in nicely since my application has other timers that need to be started on application startup.

      To use transactions I had to add these parameters to jbpm.sar/jbpm.sar.cfg.jar/jbpm.hibernate.properties:

      hibernate.transaction.factory_class=org.hibernate.transaction.JTATransactionFactory
      hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup
      


      package com.something.workflow;
      
      public interface Scheduler {
      
       void start();
       void stop();
      }
      


      package com.something.workflow.impl;
      
      import static java.lang.Math.min;
      
      import java.util.Collection;
      import java.util.Date;
      import java.util.Iterator;
      
      import javax.annotation.Resource;
      import javax.ejb.SessionContext;
      import javax.ejb.Stateless;
      import javax.ejb.Timeout;
      
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      import org.jbpm.calendar.BusinessCalendar;
      import org.jbpm.calendar.Duration;
      import org.jbpm.db.JbpmSession;
      import org.jbpm.db.JbpmSessionFactory;
      import org.jbpm.db.SchedulerSession;
      import org.jbpm.scheduler.exe.Timer;
      
      import com.something.workflow.Scheduler;
      
      @Stateless
      public class SchedulerBean implements Scheduler {
       private static final Log log = LogFactory.getLog(SchedulerBean.class);
      
       static JbpmSessionFactory jbpmSessionFactory = JbpmSessionFactory.getInstance();
       static BusinessCalendar businessCalendar = new BusinessCalendar();
      
       @Resource
       private SessionContext ctx;
      
       private static final long DEFAULT_TIMEOUT = 5000L;
      
       public void start()
       {
       log.debug("starting Scheduler");
       ctx.getTimerService().createTimer(DEFAULT_TIMEOUT,null);
       }
      
       @Timeout
       public void timeoutHandler(javax.ejb.Timer timer)
       {
       long time = min(executeTimers(),DEFAULT_TIMEOUT);
       timer.cancel();
       ctx.getTimerService().createTimer(time,null);
       }
      
       public void stop() {
       log.debug("stopping Scheduler");
       Collection timers = ctx.getTimerService().getTimers();
       for (Object o : timers) {
       javax.ejb.Timer t = (javax.ejb.Timer)o;
       t.cancel();
       }
       }
      
       private long executeTimers() {
       long millisTillNextTimerIsDue = DEFAULT_TIMEOUT;
       boolean isDueDateInPast = true;
      
       JbpmSession jbpmSession = jbpmSessionFactory.openJbpmSessionAndBeginTransaction();
       try {
       SchedulerSession schedulerSession = new SchedulerSession(jbpmSession);
      
       log.debug("checking for jBPM timers");
       Iterator iter = schedulerSession.findTimersByDueDate();
       while ((iter.hasNext()) && (isDueDateInPast)) {
       Timer timer = (Timer) iter.next();
       log.debug("found timer "+timer);
      
       // if this timer is due
       if (timer.isDue()) {
       log.debug("executing timer '"+timer+"'");
      
       // execute
       timer.execute();
      
       // if there was an exception, just save the timer
       if (timer.getException()!=null) {
       schedulerSession.saveTimer(timer);
      
       // if repeat is specified
       }
       else if (timer.getRepeat()!=null) {
       // update timer by adding the repeat duration
       Date dueDate = timer.getDueDate();
      
       // suppose that it took the timer runner thread a
       // very long time to execute the timers.
       // then the repeat action dueDate could already have passed.
       while (dueDate.getTime()<=System.currentTimeMillis()) {
       dueDate = businessCalendar.add(dueDate,new Duration(timer.getRepeat()));
       }
       timer.setDueDate( dueDate );
       // save the updated timer in the database
       log.debug("saving updated timer for repetition '"+timer+"' in '"+(dueDate.getTime()-System.currentTimeMillis())+"' millis");
       schedulerSession.saveTimer(timer);
       }
       else {
       // delete this timer
       log.debug("deleting timer '"+timer+"'");
       schedulerSession.deleteTimer(timer);
       }
       }
       else { // this is the first timer that is not yet due
       isDueDateInPast = false;
       millisTillNextTimerIsDue = timer.getDueDate().getTime() - System.currentTimeMillis();
       }
       }
       }
       finally {
       jbpmSession.commitTransactionAndClose();
       }
      
       return millisTillNextTimerIsDue;
       }
      
      }
      


      Did I duplicate anything already in jBPM? I looked around for this first, but didn't find it. Anyone see any reasons to not use this approach?

      I appreciate all advice,
      Ian