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) ; }
When the scheduled time is reached or the specified interval has
elapsed, the container system invokes the enterprise
bean’s ejbTimeout( )
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 EJB can be modified to
implement the TimedObject
interface, as shown:
public abstract class ShipBean
implements javax.ejb.EntityBean, javax.ejb.TimedObject
{
javax.ejb.EntityContext ejbContext;
public void setEntityContext(javax.ejb.EntityContext ctxt){
ejbContext = ctxt;
}
public void ejbTimeout(javax.ejb.Timer timer) {
// business logic for timer goes here
}
public Integer ejbCreate(Integer primaryKey,String name,double tonnage) {
setId(primaryKey);
setName(name);
setTonnage(tonnage);
return null;
}
public void ejbPostCreate(Integer primaryKey,String name,double tonnage) {}
public abstract void setId(Integer id);
public abstract Integer getId( );
public abstract void setName(String name);
public abstract String getName( );
public abstract void setTonnage(double tonnage);
public abstract double getTonnage( );
public void unsetEntityContext( ){}
public void ejbActivate( ){}
public void ejbPassivate( ){}
public void ejbLoad( ){}
public void ejbStore( ){}
public void ejbRemove( ){}
}
An enterprise bean schedules itself for a timed notification using a
reference to the TimerService
, which it obtains
from the EJBContext
. 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. EJBContext ejbContext = // ...: get EJBContext object from somewhere. TimerService timerService = ejbContext.getTimerService( ); timerService.createTimer( date, null);
This example creates a Calendar
object that
represents the current time, 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 EJB that allows a client to schedule a maintenance item.
When the method is called, the client passes in 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, 2004, as shown in the following code
snippet:
InitialContext jndiCntxt = new InitialContext( ); ShipHomeRemote shipHome = (ShipHomeRemote) jndiCntxt.lookup("java:comp/env/ejb/ShipHomeRemote"); ShipRemote ship = shipHome.findByName("Valhalla"); Calendar april2nd = Calendar.getInstance( ); april2nd.set(2004, Calendar.APRIL, 2); String description = "Stress Test: Test Drive Shafts A & B ..."; ship.scheduleMaintenance( description, april2nd.getTime( ) );
The ShipBean
implements the
scheduleMaintenance( )
method and takes care of scheduling the
event using the Timer Service, as shown below:
public abstract class ShipBean
implements javax.ejb.EntityBean, javax.ejb.TimedObject
{
javax.ejb.EntityContext ejbContext;
public void setEntityContext(javax.ejb.EntityContext ctxt){
ejbContext = ctxt;
}
public void scheduleMaintenance(String description, Date dateOfTest){
TimerService timerService = ejbContext.getTimerService( );
timerService.createTimer( dateOf, description);
}
public void ejbTimeout(javax.ejb.Timer timer) {
// business logic for timer goes here
}
...
}
As you can see, the Ship EJB is responsible for obtaining a reference
to the Timer Service and scheduling its own events. When April 2,
2004, rolls around, the Timer Service calls the ejbTimeout( )
method on the Ship EJB representing the Valhalla. When
the ejbTimeout( )
method is called, the Ship EJB
sends a JMS message containing the description of the test to the
Health and Safety department at Titan Cruises, alerting them that a
stress test is required. Here’s how the
implementation of ejbTimeout( )
looks:
public abstract classShipBean
implements javax.ejb.EntityBean,javax.ejb.TimedObject
{ javax.ejb.EntityContext ejbContext; public void setEntityContext(javax.ejb.EntityContext ctxt){ ejbContext = ctxt; } public void scheduleMaintenance(String description, Date dateOfTest){ TimerService timerService = ejbContext.getTimerService( ); timerService.createTimer( dateOf, description); } public void ejbTimeout(javax.ejb.Timer timer) { try{ String description = (String)timer.getInfo( ); InitialContext jndiContext = new InitialContext( ); TopicConnectionFactory factory = (TopicConnectionFactory) jndiContext.lookup("java:comp/env/jms/TopicFactory"); Topic topic = (Topic) jndiContext.lookup("java:comp/env/jms/MaintenanceTopic"); TopicConnection connect = factory.createTopicConnection( ); TopicSession session = connect.createTopicSession(true,0); TopicPublisher publisher = session.createPublisher(topic); TextMessage textMsg = session.createTextMessage( ); textMsg.setText(description); publisher.publish(textMsg); connect.close( ); }catch(Exception e){ throw new EJBException(e); } } .... }
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 a part of the javax.ejb
package in
EJB 2.1 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 durration. 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,
while an interval timer expires many times, at specified intervals.
When a timer expires, the Timer Service calls the
bean’s ejbTimeout( )
method.
Here’s how each of the four createTimer( )
methods works. At this point, we are only discussing 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,
2004:
Calendar july4th = Calendar.getInstance( ); july4th.set(2004, Calendar.JULY, 4); timerService.createTimer(july4th.getTime( ), null);
createTimer(long duration, Serializable info)
Creates a single-action timer that only expires 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, 2004 and continues to expire every
three days after that date:
Calendar july4th = Calendar.getInstance( ); july4th.set(2004, 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 vendors’ 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 EJB
defines the clearSchedule( )
method, which allows
a client to cancel all of the timers on a specific ship.
Here’s the implementation of clearSchedule( )
:
public abstract class ShipBean
implements javax.ejb.EntityBean, javax.ejb.TimedObject
{
javax.ejb.EntityContext ejbContext;
public void setEntityContext(javax.ejb.EntityContext ctxt){
ejbContext = ctxt;
}
public void clearSchedule( ){
TimerService timerService = ejbContext.getTimerService( );
java.util.Iterator timers = timerService.getTimers( ).iterator( );
while( timers.hasNext( ) ){
javax.ejb.Timer timer = (javax.ejb.Timer) timers.next( );
timer.cancel( );
}
}
public void scheduleMaintenance(String description, Date dateOfTest){
// code for scheduling timer 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 an iterator that contains all
of the Timer
s. Then we work through the iterator,
cancelling each timer as we go. The Timer
objects
implement a cancel( )
method, which removes the
timed event from the Timer Service so that it never expires.
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
. The
reasons that the TimerService
methods would throw
these exceptions are:
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.
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. 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.
The
previous
section used the Timer.cancel( )
method.
It’s used to cancel a specific timer: remove the
timed event 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.
public abstract class ShipBean
implements javax.ejb.EntityBean, javax.ejb.TimedObject
{
javax.ejb.EntityContext ejbContext;
public void setEntityContext(javax.ejb.EntityContext ctxt){
ejbContext = ctxt;
}
public void scheduleMaintenance(String description, Date dateOfTest){
TimerService timerService = ejbContext.getTimerService( );
java.util.Iterator timers = timerService.getTimers( ).iterator( );
while( timers.hasNext( ) ){
javax.ejb.Timer timer = (javax.ejb.Timer) timers.next( );
String timerDesc = (String) timer.getInfo( );
if( description.equals( timerDesc)){
timer.cancel( );
}
}
timerService.createTimer( dateOf, description);
}
public void ejbTimeout(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 while loop, the new Timer
is added to the
Timer Service.
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
with by the Timer Service and delivered to the enterprise bean when
its ejbTimeout( )
method is called. The
serializable object used as the info
parameter can
be anything, as long it implements the
java.io.Serializable
interface and follows the
rules of serialization.[44] The info object can
be put to many uses, but one obvious use is to associate the timer
with some sort of identifier.
To get the info object from a timer, 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, rather than compare
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 description; public MaintenanceItem(long min, String desc){ maintenanceItemNumber = min; description = desc; } public long getMIN( ){ return maintenanceItemNumber; } public String getDescription( ){ return description; } }
Using the MaintenanceItem
type, we can modify the
scheduleMaintenance( )
method to be more precise,
as shown below (changes are in bold):
public abstract class ShipBean implements javax.ejb.EntityBean,javax.ejb.TimedObject
{ javax.ejb.EntityContext ejbContext; public void setEntityContext(javax.ejb.EntityContext ctxt){ ejbContext = ctxt; } public void scheduleMaintenance(MaintenanceItem maintenanceItem,
Date dateOfTest){ TimerService timerService = ejbContext.getTimerService( ); java.util.Iterator timers = timerService.getTimers( ).iterator( ); while( timers.hasNext( ) ){ javax.ejb.Timer timer = (javax.ejb.Timer) timers.next( ); MaintenanceItem timerMainItem = (MaintenanceItem) timer.getInfo( ); if( maintenanceItem.getMIN( ) == timerMainItem.getMIN( )){ timer.cancel( );}
} timerService.createTimer( dateOf,maintenanceItem
); } public void ejbTimeout(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 ejbTimeout( )
method on
the Ship EJB. When the ejbTimeout( )
method is
called, the info object is obtained from the Timer object and used to
determine which timer logic should be executed.
Exercise 13.1 in the Workbook shows how to deploy these examples in JBoss.
The Timer.getNextTimeout( )
method
simply returns the date—represented
by a java.util.Date
instance—on 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 Timer.getHandle( )
method returns a
TimerHandle
. The
TimerHandle
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, 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.
[44] 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 handle) are marked as
transient
.