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