EJB3 Timer Scheduler
iterrell Dec 29, 2005 3:49 PMI'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