Tuesday, July 27, 2010

EJB3.1 TimerService in JBoss AS 6.0.0.M4

For those of you, who have been waiting for more of EJB3.1 features in JBoss AS, here's some good news! JBoss AS 6.0.0.M4 which will be released in a day or two, introduces support for EJB3.1 Timer Service and EJB3.1 Asynchronous invocations. In this article, we'll see some examples on EJB3.1 timer service features.


Download JBoss AS:

Before, starting with the example, let's first download JBoss AS 6.0.0.M4. You can download it from here. After downloading the AS, start and stop it once to make sure it boots without any issues.

In the next steps we will see a simple EJB3.1 timerservice example (mainly the calendar expression support)

EJB3.1 Timer Service:

Note that the examples shown below are just for the sake of illustrating the usage of timerservice API and as such don't hold much meaning.

Let's consider a very simple stateless session bean which exposes a remote business interface view:


 
package org.jboss.ejb3.timerservice.example;

public interface TimerOps
{

/**
* Schedules a timer for the passed calendarSchedule
*
* @param calendarSchedule The calendar expression based schedule
* @param taskName The name of the task, which will be passed an a info to the {@link Timer}
*
*/
void scheduleTask(ScheduleExpression calendarSchedule, String taskName);


}





 
package org.jboss.ejb3.timerservice.example;

import java.util.Date;

import javax.annotation.Resource;
import javax.ejb.Remote;
import javax.ejb.ScheduleExpression;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;

import org.jboss.ejb3.annotation.RemoteBinding;

@Stateless
@Remote (TimerOps.class)
@RemoteBinding (jndiBinding = "timerservice-example-scheduler-remote")
public class Scheduler implements TimerOps
{

@Resource
private TimerService timerService;

@Override
public void scheduleTask(ScheduleExpression calendarSchedule, String taskName)
{
// TimerConfig can be passed to the timer service, during timer creation,
// to specify whether the timer is persistent and also pass any additional
// Serializable info.
TimerConfig timerConfig = new TimerConfig();

// by default, timers are persistent. So we don't really have to
// set the persistence property on the TimerConfig. It's done here,
// just to demonstrate the usage
timerConfig.setPersistent(true);

// set the "info" to be passed to the timers
timerConfig.setInfo(taskName);

// Now, use the injected timerservice to create a timer instance,
// for the passed schedule and the timer config
Timer timer = this.timerService.createCalendarTimer(calendarSchedule, timerConfig);

// that's it, we are done. The timer is now created and started (on successful completion
// of this method). When the scheduled timeout occurs, a method in this bean which is marked
// as the timeout callback method will be invoked.
}

/**
* This method will be invoked by the server when a scheduled timeout occurs for timer(s)
* created through the timerservice corresponding to this bean.
*
* @param timer The {@link Timer} for which the timeout occurred
*/
@Timeout
private void onTimeout(Timer timer)
{
// do some task here. for now, let's just print out to System.out
System.out.println("Timeout method invoked at " + new Date() + " for bean " + this.getClass().getSimpleName());
}

}



So in the example above, we have a TimerOps interface which is exposed as the remote view for the Scheduler Stateless session bean. The TimerOps interface allows scheduling timers based on a javax.ejb.ScheduleExpression. This ScheduleExpression is used to express calendar based timeout expressions. We will see more of it, later in this article.

Moving on to the Scheduler SLSB, you will notice that it expects the TimerService to be injected:

 

@Resource
private TimerService timerService;



The injection of timer service into beans, requires no more than annotating that field with the @javax.annotation.Resource annotation. The bean will then use the injected timerservice to create and operate on the timers.

Let's look at the scheduleTask method in that bean:

 
@Override
public void scheduleTask(ScheduleExpression calendarSchedule, String taskName)
{
// TimerConfig can be passed to the timer service, during timer creation,
// to specify whether the timer is persistent and also pass any additional
// Serializable info.
TimerConfig timerConfig = new TimerConfig();

// by default, timers are persistent. So we don't really have to
// set the persistence property on the TimerConfig. It's done here,
// just to demonstrate the usage
timerConfig.setPersistent(true);

// set the "info" to be passed to the timers
timerConfig.setInfo(taskName);

// Now, use the injected timerservice to create a timer instance,
// for the passed schedule and the timer config
Timer timer = this.timerService.createCalendarTimer(calendarSchedule, timerConfig);

// that's it, we are done. The timer is now created and started (on successful completion
// of this method). When the scheduled timeout occurs, a method in this bean which is marked
// as the timeout callback method will be invoked.
}


It doesn't do much, actually. It first creates (an optional) javax.ejb.TimerConfig to pass during timer creation. The TimerConfig, is where you specify whether the timer has to be persistent and/or any additional Serializable "info" that needs to be associated with the Timer, so that it is available in the timeout callback method.

In our example, above, we set the timer to be persistent (that's the default anyway) and also set the "info" to the taskName that was passed. This can be any Serializable info. Here we are just passing around some dummy String, for the sake of demonstrating the API usage.

Then, we use the injected timerservice to create a calendar timer. A calendar timer is a Timer which is created based on a calendar schedule expression. A calendar schedule expression is represented by a ScheduleExpression which expresses a schedule based on the following attributes:
 
1) dayOfMonth : Specifies the day of month when the timeout should occur
Default Value = "*" (which means, any day of month)
Example values = Last, 1, 2, 31, 28 etc...

2) dayOfWeek : Specifies the day of week when the timeout should occur
Default Value = "*" (which means, any day of month)
Example values = Sun, Wed, 2, 7 etc...

3) hour : Specifies the hour when the timeout should occur
Default Value = "0"
Example values = 12, 23, 18, 2 etc...

4) minute : Specifies the minute when the timeout should occur
Default value = "0"
Example values = 10, 12, 35, 59 etc...

5) second : Specifies the second when the timeout should occur
Default Value = "0";
Example values = 10, 15, 20 etc...

6) month : Specifies the month when the timeout should occur
Default value = * (any month)
Example values = Jan, Mar, 4, 6 etc...

7) year : Specifies the year when the timeout should occur
Default value = * (any year)
Example values = 2010, 2020 etc...

Please refer to the EJB3.1 spec for more details on ScheduleExpression.

So, for example, if we want to schedule a timer to fire every Monday at 3:30 in the morning, then we can create the following ScheduleExpression:

 
ScheduleExpression everyMondayThreeThirty = new ScheduleExpression();
everyMondayThreeThirty.dayOfWeek("Mon").hour(3).minute(30);



Okay, so let's back to our Scheduler bean now. This is where we create the timer in the bean:

 
Timer timer = this.timerService.createCalendarTimer(calendarSchedule, timerConfig);


So once the Scheduler bean's schedule method completes, the timer will be started (remember, timers are transactional, so the timer start waits for the transaction to complete). Once the timeout occurs for the timer, the server will invoke the timeout callback method on the bean. In our example, we have annotated our onTimeout() method as the timeout callback method (note the @Timeout annotation):

 
/**
* This method will be invoked by the server when a scheduled timeout occurs for timer(s)
* created through the timerservice corresponding to this bean.
*
* @param timer The {@link Timer} for which the timeout occurred
*/
@Timeout
private void onTimeout(Timer timer)
{
// do some task here. for now, let's just print out to System.out
System.out.println("Timeout method invoked at " + new Date() + " for bean " + this.getClass().getSimpleName());
}




So that was our bean and its remote interface. Now let's take a quick look at our client code which looks up the bean and schedules the timer:

 
package org.jboss.ejb3.timerservice.example.client;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.jboss.ejb3.timerservice.example.*;

public class Client
{
public static void main(String[] args) throws Exception
{

Context ctx = new InitialContext();
// lookup the bean
TimerOps scheduler = (TimerOps) ctx.lookup("timerservice-example-scheduler-remote");

// create a schedule
// Let's say, we want to fire a timeout every Monday at 3, 6 and 9 in the morning
ScheduleExpression everyMonAtThreeSixAndNineAM = new ScheduleExpression();
everyMonAtThreeSixAndNineAM.dayOfMonth("Mon").hour("3,6,9");

// schedule our task
scheduler.scheduleTask(everyMonAtThreeSixAndNineAM, "Hello World Timer");

}

}


In the client code, we first lookup the TimerOps remote business view of the bean. We then create a calendar schedule which is expected to fire on every Monday at 3, 6 and 9 in the morning. We then use the bean to schedule the task. That's it. On Monday, every morning 3AM, 6AM and 9AM, you will see the timeout being fired.

What next?

The above example was just one part of the new features in EJB3.1 timer service. Here's another - let's assume that you want to avoid having an additional method on the bean just to create a timer. Let's say that you want a timer to be created and scheduled to fire every day at 8:30 in the morning, from the time the bean is deployed. EJB3.1 timer service allows that to be done! They are known as auto timers. Here's an example of the same (using annotations):

 
package org.jboss.ejb3.timerservice.example;

import javax.ejb.Schedule;
import javax.ejb.ScheduleExpression;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import javax.naming.Context;
import javax.naming.InitialContext;

@Stateless
public class AutoScheduler
{

/**
* This method will be invoked every day at 8:30:00 in the morning
* @param timer The timer instance
*/
@Schedule (hour = "8", minute = "30")
private void executeEveryDayAtEightThirty(Timer timer)
{
// do some task here. for now, let's just print out to System.out
System.out.println("(Auto) timer method invoked at " + new Date() + " for bean " + this.getClass().getSimpleName());

}

}



As you can see we have a Stateless (no-interface view) bean which just has this executeEveryDayAtEightThirty method. The important bit here is the @javax.ejb.Schedule annotation which is used on that method. The Schedule annotation is used to create auto timers. The server will look for methods on the bean with the @Schedule annotation and automatically create the timers when the bean is deployed. The schedule for the timers is passed through the annotation's attribute values. The method on which the @Schedule is used, is considered as the timeout callback method for that auto timer and will be invoked when the timeout occurs.

In our example above, we are creating an auto-timer which will fire every day at 8:30 in the morning and the executeEveryDayAtEightThirty method will be called.

Want some more?

Those were just some of the examples for EJB3.1 timer service. There's much more you can try out. For example, there's also the @Schedules (note that it's not the same as @Schedule), which can be used to create multiple auto-timers for the same timeout callback method. Furthermore, if you don't want to use annotations, you can as well use the deployment descriptors. We also haven't covered the other APIs on the TimerService or the Timer in these examples. We can go on and on, but let's keep this short for now.
But, do try out these new features and let us know if you run into any issues. Feel free to start a discussion about any issues around this, in our EJB3 User Forum or ping us on IRC.