My blog has moved!

You should automatically be redirected in 6 seconds. If not, visit
http://blogs.i2m.dk/allan
and update your bookmarks.

Monday 17 March 2008

Creating timers in EJB3

While the EJB 3.1 expert group is working on the improved timer service using annotations (See New Features in EJB 3.1) I thought that I'd just bring a small entry on using the timer service in EJB 3.

The timer service works by telling the service when it should timeout (i.e. when shall the "alarm" go off). You can add to this by telling it when it should timeout the first time, and how often (in ms) it should timeout after that. You define which methods on the bean that should be invoked upon timeout by annotating them @Timeout. The timer service is initialised by annotating a TimerService object as a @Resource.

Okay, before I show the code, these are the methods that we need:


  • A method for starting the timer

  • A method for stopping the timer

  • One or more listener methods that will be invoked when the timer has timed-out




@Stateless
public class MyTimerBean implements MyTimerLocal {

/** Service used for scheduling tasks. */
@Resource private TimerService timerService;

/**
* Starts the scheduler.
*
* @param startDate
* Start date
* @param interval
* Interval at which the timeout shall repeat
* @param timerName
* Timer to start
*/
public void startTimer(Date startDate, Long interval, String timerName) {
this.timerService.createTimer(startDate, interval, timerName);
}

/**
* Stops a given scheduler.
*
* @param timerName
* Timer to stop
*/
public void stopTimer(String timerName) {
for (Timer timer : (Collection) this.timerService.getTimers()) {
if (timer.getInfo() instanceof String) {
if (((String) timer.getInfo()).equals(timerName)) {
timer.cancel();
return;
}
}
}
}

/**
* {@link Timeout} event handler for generating a report.
*
* @param timer
* Timer that timed out
*/
@Timeout
public void generateReport(Timer timer) {
if (timer.getInfo() instanceof String) {
if (((String) timer.getInfo()).equals("Generate Report")) {
... do some processing ...
}
}
}

/**
* {@link Timeout} event handler for cleaning the cache.
*
* @param timer
* Timer that timed out
*/
@Timeout
public void cleanCache(Timer timer) {
if (timer.getInfo() instanceof String) {
if (((String) timer.getInfo()).equals("Clean Cache")) {
... do some processing ...
}
}
}
}


Right, so we have a method for starting a timer (startTimer). This method needs to be invoked in order to start the timer. This is one of the drawbacks of the TimerService, you cannot tell it to just start when the application is deployed (will be there in EJB3.1). Instead I use a Servlet Context Listener to invoke the startTimer method when the accompaying webapplication is deployed:


public class TimerInitialisationListener implements ServletContextListener {

/** Local interface for {@link MyTimerBean}. */
@EJB private MyTimerLocal myTimer;

/**
* Initialises the timer service.
*
* @param event
* Event that invoked the listener
*/
public void contextInitialized(ServletContextEvent event) {
Calendar now = Calendar.getInstance();
now.set(Calendar.HOUR_OF_DAY, 0);
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
int year = now.get(Calendar.YEAR);
int month = now.get(Calendar.MONTH);
int dayOfMonth = now.get(Calendar.DAY_OF_MONTH);
int hourOfDay = now.get(Calendar.HOUR_OF_DAY);
int minute = now.get(Calendar.MINUTE);
Long repeat = 60000L * 60L * 24L;
LogFactory.getLog(TimerInitialisationListener.class).info("Start time: " + now.getTime());
LogFactory.getLog(TimerInitialisationListener.class).info("Repeat every: " + repeat + " ms (" + (repeat / 3600000L) + " hrs)");

myTimer.startTimer(new GregorianCalendar(year, month, dayOfMonth, hourOfDay, minute).getTime(), 60000L * 60L * 24L, "Generate Report");
}

/**
* Context is uninstalled from the servlet container.
*
* @param event
* Event that invoked the listener
*/
public void contextDestroyed(ServletContextEvent event) {
LogFactory.getLog(TimerInitialisationListener.class).info("Stopping timer");
myTimer.stopTimer("Generate Report");
}
}


When you deploy the enterprise application you will see that the timer is set to start at midnight and execute every 86400000 ms (i.e. every 24 hours). What you will notice is that it is only the generateReport method that is executed fully at every timeout as I've put in a check to ensure that it is the correct action being executed. Instead of using a String as the identifier of the Timer you can create create your own custom objects as pass them instead (just remember to make it serializable).

That's all for now. I'll bring another entry when EJB3.1 has been released and the new timer service annotations have been implemented.

On a side note, I've used Quartz before and it's great - I just like to stick to the standards if it can do the job.

Checkout the JavaDocs for the TimerService for more information.

UPDATE: 6. May 2008: Yesterday I was preparing some code using the timer service and I noticed that only the first method annotated with @TimeOut is executed upon timeout. Therefore, use only one @TimeOut method per SessionBean.

No comments: