Timer Service API




Timer Service API

The Timer Service enables an enterprise bean to be notified when a specific date has arrived, when some period of time has elapsed, or at recurring intervals. To use the Timer Service, an enterprise bean must implement the javax.ejb.TimedObject interface, which defines a single callback method, ejbTimeout( ):

package javax.ejb;

public interface TimedObject {
    public void ejbTimeout(Timer timer) ;
}

In EJB 3.0, the @javax.ejb.Timeout annotation can be applied to a method whose signature returns void and has one javax.ejb.Timer parameter. We'll show examples of both.

When the scheduled time is reached or the specified interval has elapsed, the container system invokes the enterprise bean's timeout callback method. The enterprise bean can then perform any processing it needs to respond to the timeout, such as run reports, audit records, modify the states of other beans, etc. For example, the Ship Maintenance EJB can be modified to implement the TimedObject interface, as shown:

@Stateless
public class ShipMaintenanceBean implements ShipMaintenanceRemote
      implements javax.ejb.TimedObject {

    public void ejbTimeout(javax.ejb.Timer timer) {
       // business logic for timer goes here
    }
}

Alternatively, the @javax.ejb.Timeout annotation can be used:

@Stateless
public class ShipMaintenanceBean implement ShipMaintenanceRemote {

    @Timeout
    public void maintenance(javax.ejb.Timer timer) {
       // business logic for timer goes here
    }
}

An enterprise bean schedules itself for a timed notification using a reference to the TimerService , which can be obtained from the EJBContext or injected directly into your bean using the @javax.annotation.Resource annotation. The TimerService allows a bean to register itself for notification on a specific date, after some period of time, or at recurring intervals. The following code shows how a bean would register for notification exactly 30 days from now:

// Create a Calendar object 
 that represents the time
30 days from now.
Calendar time = Calendar.getInstance( );  // the current time.
time.add(Calendar.DATE, 30); // add 30 days to the current time.
Date date = time.getTime( );

// Create a timer that will go off 30 days from now.
TimerService timerService = // from EJBContext or injected
timerService.createTimer( date,  null);

This example creates a Calendar object that represents the current time and then increments this object by 30 days so that it represents the time 30 days from now. The code obtains a reference to the container's TimerService and calls the TimerService.createTimer( ) method, passing it the java.util.Date value of the Calendar object, thus creating a timer that will go off after 30 days.

We can add a method, scheduleMaintenance( ), to the Ship Maintenance EJB that allows a client to schedule a maintenance item for a particular ship. When the method is called, the client passes in the name of the ship on which maintenance will be performed, a description of the maintenance item, and the date on which it is to be performed. For example, a client could schedule a maintenance item for the cruise ship Valhalla on April 2, 2006, as shown in the following code snippet:

InitialContext jndiCntxt = new InitialContext( );
ShipMaintenanceRemote maintenance =
  (ShipMaintenanceRemote) jndiCntxt.lookup("ShipMaintenanceRemote ");
ShipMaintenanceRemote Calendar april2nd = Calendar.getInstance( );
april2nd.set(2006, Calendar.APRIL, 2);
String description = "Stress Test: Test Drive Shafts A & B ...";
maintenance.scheduleMaintenance("Valhalla", description, april2nd.getTime( ));

The ShipMaintenanceBean implements the scheduleMaintenance( ) method and takes care of scheduling the event using the Timer Service, as shown here:

@Stateless
public class ShipMaintenanceBean implements ShipMaintenanceRemote{

    @Resource javax.ejb.TimerService timerService;
    @PersistenceContext(unitName="titanDB") entityManager;

 public void scheduleMaintenance(String ship, String description,
                                    Date dateOfTest) {
        String item = ship + " is scheduling maintenance of " + description;
        timerService.createTimer(dateOf, msg);
    }

    @Timeout
    public void maintenance(javax.ejb.Timer timer) {
       // business logic for timer goes here
    }
    ...
}

As you can see, the Ship Maintenance EJB is responsible for obtaining a reference to the Timer Service and scheduling its own events. When April 2, 2006 rolls around, the Timer Service calls the annotated maintenance( ) method on the Ship Maintenance EJB representing the Valhalla. When this callback method is invoked, the Ship Maintenance EJB sends a JMS message containing the description of the test to the Health and Safety Department at Titan Cruises, alerting it that a stress test is required. Here's how the implementation of maintenance looks:

@Stateless
public class ShipMaintenanceBean implements ShipMaintenanceRemote {

    @Resource javax.ejb.TimerService timerService;

    public void scheduleMaintenance(String ship, String description,
                                    Date dateOfTest) {
        String item = ship + " is scheduling maintenance of " + description;
        timerService.createTimer(dateOf, item);
    }

    @Resource(mappedName="ConnectionFactory") ConnectionFactory factory;
    @Resource(mappedName="MaintenanceTopic") topic;

 @Timeout
    public void maintenance(javax.ejb.Timer timer) {
      try{
        String item = (String)timer.getInfo( );

        Connection connect = factory.createConnection( );
        Session session = connect.createSession(true,0);
        MessageProducer publisher = session.createProducer(topic);

        TextMessage msg = session.createTextMessage( );
        msg.setText(item);
        publisher.send(msg);
        connect.close( );
      }catch(Exception e){
        throw new EJBException(e);
      }
    }
}

The TimerService Interface

The TimerService interface provides an enterprise bean with access to the EJB container's Timer Service so that new timers can be created and existing timers can be listed. The TimerService interface is part of the javax.ejb package in EJB 3.0 and has the following definition:

package javax.ejb;
import java.util.Date;
import java.io.Serializable; 

public interface TimerService {

   // Create a single-action 
 timer that
expires on a specified date.
   public Timer createTimer(Date expiration, Serializable info)
 

      throws IllegalArgumentException,IllegalStateException,EJBException;
   // Create a single-action timer that expires after a specified duration.
 

   public Timer createTimer(long duration, Serializable info)
 

      throws IllegalArgumentException,IllegalStateException,EJBException;
   // Create an interval 
 timer that starts on a specified date.
   public Timer createTimer(
      Date initialExpiration, long intervalDuration, Serializable info)
      throws IllegalArgumentException,IllegalStateException,EJBException;
   // Create an interval timer that starts after a specified duration.
   public Timer createTimer(
      long initialDuration, long intervalDuration, Serializable info)
      throws IllegalArgumentException,IllegalStateException,EJBException;
   // Get all the active timers associated with this bean
   public java.util.Collection getTimers( )
      throws IllegalStateException,EJBException;
}

Each of the four TimerService.createTimer( ) methods establishes a timer with a different type of configuration. There are essentially two types of timers: single-action and interval. A single-action timer expires once, and an interval timer expires many times, at specified intervals. When a timer expires, the Timer Service calls the bean's ejbTimeout( ) method or a callback method with @javax.ejb.Timeout .

Here's how each of the four createTimer( ) methods works. At this point, we are discussing only the expiration and duration parameters and their uses. The Serializable info parameter is discussed later in this chapter.


createTimer(Date expiration, Serializable info)

Creates a single-action timer that expires once. The timer expires on the date set for the expiration parameter. Here's how to set a timer that expires on July 4, 2006:

Calendar july4th = Calendar.getInstance( );
july4th.set(2006, Calendar.JULY, 4);
timerService.createTimer(july4th.getTime( ), null);


createTimer(long duration, Serializable info)

Creates a single-action timer that expires only once. The timer expires after duration time (measured in milliseconds) has elapsed. Here's how to set a timer that expires in 90 days:

long ninetyDays = 1000 * 60 * 60 * 24 * 90; // 90 days
timerService.createTimer(ninetyDays, null);


createTimer(Date initialExpiration, long intervalDuration, Serializable info)

Creates an interval timer that expires many times. The timer first expires on the date set for the initialExpiration parameter. After the first expiration, subsequent expirations occur at intervals equal to the intervalDuration parameter (in milliseconds). Here's how to set a timer that expires on July 4, 2006 and continues to expire every three days after that date:

Calendar july4th = Calendar.getInstance( );
july4th.set(2006, Calendar.JULY, 4);
long threeDaysInMillis = 1000 * 60 * 60 * 24 * 3; // 3 days
timerService.createTimer(july4th.getTime( ), threeDaysInMillis, null);


createTimer(long initialDuration, long intervalDuration, Serializable info)

Creates an interval timer that expires many times. The timer first expires after the period given by initialDuration has elapsed. After the first expiration, subsequent expirations occur at intervals given by the intervalDuration parameter. Both initialDuration and intervalDuration are in milliseconds. Here's how to set a timer that expires in 10 minutes and continues to expire every hour thereafter:

long tenMinutes = 1000 * 60 * 10;  // 10 minutes
long oneHour = 1000 * 60 * 60; // 1 hour
timerService.createTimer(tenMinutes, oneHour, null);

When a timer is created, the Timer Service makes it persistent in some type of secondary storage, so it will survive system failures. If the server goes down, the timers are still active when the server comes back up. While the specification isn't clear, it's assumed that any timers that expire while the system is down will go off when it comes back up again. If an interval timer expires many times while the server is down, it may go off multiple times when the system comes up again. Consult your vendor's documentation to learn how they handle expired timers following a system failure.

The TimerService.getTimers( ) method returns all the timers that have been set for a particular enterprise bean. For example, if this method is called on the EJB representing the cruise ship Valhalla, it returns only the timers that are set for the Valhalla, not timers set for other ships. The getTimers( ) method returns a java.util.Collection , an unordered collection of zero or more javax.ejb.Timer objects. Each Timer object represents a different timed event that has been scheduled for the bean using the Timer Service.

The getTimers( ) method is often used to manage existing timers. A bean can look through the Collection of Timer objects and cancel any timers that are no longer valid or need to be rescheduled. For example, the Ship Maintenance EJB defines the clearSchedule( ) method, which allows a client to cancel all scheduled maintenance. Here's the implementation of clearSchedule( ):

@Stateless
public class ShipMaintenanceBean implements ShipMaintenanceRemote {

    @Resource javax.ejb.TimerService timerService;

    public void clearSchedule( ) {
        for (Object obj : timerService.getTimers( )) {
            javax.ejb.Timer 
 timer = (javax.ejb.Timer)obj;
         timer.cancel( );
        }
    }

    public void scheduleMaintenance(String name, String desc, Date date) {
       // business logic goes here

    public void ejbTimeout(javax.ejb.Timer timer) {
       // business logic for timer goes here
    }
    ...
}

The logic here is simple. After getting a reference to the TimerService , we get a Collection that contains all of the Timers. Then we loop through the Collection, canceling each timer whose MaintenanceItem is the desired ship. The Timer objects implement a cancel( ) method, which removes the timed event from the Timer Service so that it never expires.

Exceptions

The TimerService.getTimers( ) method can throw an IllegalStateException or an EJBException . All of the createTimer( ) methods declare these two exceptions, plus a third exception, the IllegalArgumentException . Here are the reasons why the TimerService methods would throw these exceptions:


java.lang.IllegalArgumentException

The duration and expiration parameters must have valid values. This exception is thrown if a negative number is used for one of the duration parameters or a null value is used for the expiration parameter, which is of type java.util.Date .


java.lang.IllegalStateException

This exception is thrown if the enterprise bean attempts to invoke one of the TimerService methods from a method where it's not allowed. Each enterprise bean type (i.e., entity, stateless session, and message driven) defines its own set of allowed operations. However, in general, the TimerService methods can be invoked from anywhere except the EJBContext methods (i.e., setEntityContext( ), setSessionContext( ), and setMessageDrivenContext( )).


javax.ejb.EJBException

This exception is thrown when some type of system-level exception occurs in the Timer Service.

The Timer

A Timer is an object that implements the javax.ejb.Timer interface. It represents a timed event that has been scheduled for an enterprise bean using the Timer Service. Timer objects are returned by the TimerService.createTimer( ) and TimerService.getTimers( ) methods, and a Timer is the only parameter of the TimedObject.ejbTimeout( ) method or annotated @javax.ejb.Timeout callback. The Timer interface is:

package javax.ejb;

public interface Timer {

    // Cause the timer and all its associated expiration
    // notifications to be canceled
    public void cancel( )
         throws IllegalStateException,NoSuchObjectLocalException,EJBException;

    // Get the information associated with the timer at the time of creation.
    public java.io.Serializable getInfo( )
         throws IllegalStateException,NoSuchObjectLocalException,EJBException;

    // Get the point in time at which the next timer
    // expiration is scheduled to occur.
    public java.util.Date getNextTimeout( )
         throws IllegalStateException,NoSuchObjectLocalException,EJBException;

    // Get the number of milliseconds that will elapse
    // before the next scheduled timer expiration
    public long getTimeRemaining( )
         throws IllegalStateException,NoSuchObjectLocalException,EJBException;

    //Get a serializable handle to the timer
    public TimerHandle getHandle( )
         throws IllegalStateException,NoSuchObjectLocalException,EJBException;
}

A Timer instance represents exactly one timed event and can be used to cancel the timer, obtain a serializable handle, obtain the application data associated with the timer, and find out when the timer's next scheduled expiration will occur.

Canceling timers

The previous section used the Timer.cancel( ) method. It's used to cancel a specific timer from the Timer Service so that it never expires. It is useful if a particular timer needs to be removed completely or simply rescheduled. To reschedule a timed event, cancel the timer and create a new one. For example, when one of the ship's components fails and is replaced, that component must have its maintenance rescheduled: it doesn't make sense to perform a yearly overhaul on an engine in June if it was replaced in May. The scheduleMaintenance( ) method can be modified so that it can add a new maintenance item or replace an existing one by canceling it and adding the new one:

package com.titan.maintenance;

import javax.ejb.*;
import java.util.Date;
import javax.annotation.Resource;

@Stateless
public class ShipMaintenanceBean implements ShipMaintenanceRemote {

    @Resource javax.ejb.TimerService timerService;

 public void scheduleMaintenance(String ship, String description,
                                    Date dateOf) {
        String item = ship + " is scheduling maintenance of " + description;

        for (Object obj : timerService.getTimers( )) {
            javax.ejb.Timer timer = (javax.ejb.Timer)obj;
            String scheduled = (String)timer.getInfo( );
            if (scheduled.equals(item)) {
                timer.cancel( );
            }
        }

        timerService.createTimer(dateOf, item);
    }

    @Timeout
    public void maintenance(javax.ejb.Timer timer) {
       // business logic for timer goes here
    }
}

The scheduleMaintenance( ) method first obtains a Collection of all timers defined for the ship. It then compares the description of each timer to the description passed into the method. If there is a match, it means a timer for that maintenance item was already scheduled and should be canceled. After the for loop, the new Timer is added to the Timer Service.

Identifying timers

Of course, comparing descriptions is a fairly unreliable way of identifying timers, since descriptions tend to vary over time. What is really needed is a far more robust information object that can contain both a description and a precise identifier.

All of the TimeService.createTimer( ) methods declare an info object as their last parameter. The info object is application data that is stored by the Timer Service and delivered to the enterprise bean when its timeout callback is invoked. The serializable object used as the info parameter can be anything, as long as it implements the java.io.Serializable interface and follows the rules of serialization.[*] The info object can be put to many uses, but one obvious use is to associate the timer with some sort of identifier.

[*] In the most basic cases, all an object needs to do to be serializable is implement the java.io.Serializable interface and make sure any nonserializable fields (e.g., JDBC connection handles) are marked as transient.

To get the info object from a timer, you call the timer's getInfo( ) method. This method returns a serializable object, which you'll have to cast to an appropriate type. So far, we've been using strings as info objects, but there are much more elaborate (and reliable) possibilities. For example, instead of comparing maintenance descriptions to find duplicate timers, Titan decided to use unique Maintenance Item Numbers (MINs). MINs and maintenance descriptions can be combined into a new MaintenanceItem object:

public class MaintenanceItem implements java.io.Serializable {
    private long maintenanceItemNumber;
    private String shipName;
    private String description;
    public MaintenanceItem(long min, String ship, String desc) {
        maintenanceItemNumber = min;
        shipName = ship;
        description = desc;
    }
    public long getMIN( ) {
        return maintenanceItemNumber;
    }
    public String getShipName( ) {
        return shipName;
    }
    public String getDescription( ) {
        return description;
    }
}

Using the MaintenanceItem type, we can modify the scheduleMaintenance( ) method to be more precise, as shown here (changes are in bold):

@Stateless
public class ShipMaintenanceBean implements ShipMaintenanceRemote {

    @Resource javax.ejb.TimerService timerService;

    public void scheduleMaintenance(
             MaintenanceItem maintenanceItem, Date dateOfTest) {
        for (Object obj : timerService.getTimers( )) {
         MaintenanceItem scheduled = (MaintenanceItem)timer.getInfo( );
            if (scheduled.getMIN() == maintenanceItem.getMIN( )) {
                Timer.cancel( );
            }
        }
    }

    @Timeout
    public void maintenance(javax.ejb.Timer timer) {
        // business logic for timer goes here
    }

}

The MaintenanceInfo class contains information about the maintenance work that is to be done and is sent to the maintenance system using JMS. When one of the timers expires, the Timer Service calls the maintenance( ) method on the Ship Maintenance EJB. When the maintenance( ) method is called, the info object is obtained from the Timer object and used to determine which timer logic should be executed.

Retrieving other information from timers

The Timer.getNextTimeout( ) method simply returns the daterepresented by a java.util.Date instanceon which the timer will expire next. If the timer is a single-action timer, the Date returned is the time at which the timer will expire. If, however, the timer is an interval timer, the Date returned is the time remaining until the next expiration. Oddly, there is no way to determine subsequent expirations or the interval at which an interval timer is configured. The best way to handle this is to put that information into your info object.

The Timer.getTimeRemaining( ) method returns the number of milliseconds before the timer will next expire. Like the getNextTimeout( ) method, this method only provides information about the next expiration.

The TimerHandle object

The Timer.getHandle( ) method returns a TimerHandle object. The TimerHandle object is similar to the javax.ejb.Handle and javax.ejb.HomeHandle discussed in Chapter 5. It's a reference that can be saved to a file or some other resource and then used later to regain access to the Timer. The TimerHandle interface is simple:

package javax.ejb;
public interface TimerHandle extends java.io.Serializable {
    public Timer getTimer( ) throws NoSuchObjectLocalException, EJBException;
}

The TimerHandle is only valid as long as the timer has not expired (if it's a single-action timer) or been canceled. If the timer no longer exists, calling the TimerHandle.getTimer( ) method throws a javax.ejb.NoSuchObjectException .

TimerHandle objects are local, which means they cannot be used outside the container system that generated them. Passing the TimerHandle as an argument to a remote or endpoint interface method is illegal. However, a TimerHandle can be passed between local enterprise beans using their local interface, because local enterprise beans must be co-located in the same container system.

Exceptions

All the methods defined in the Timer interface declare two exceptions:


javax.ejb.NoSuchObjectLocalException

This exception is thrown if you invoke any method on an expired single-action timer or a canceled timer.


javax.ejb.EJBException

This exception is thrown when some type of system-level exception occurs in the Timer Service.