Stateless session bean timers can be used for auditing or batch processing . As an auditing agent, a stateless session timer can monitor the state of the system to ensure that tasks are being completed and that data is consistent. This type of work spans entities and possibly data sources. Such EJBs can also perform batch processing work such as database clean up, transfer of records, etc. Stateless session bean timers can also be deployed as agents that perform some type of intelligent work on behalf of the organization they serve. An agent can be thought of as an extension of an audit: it monitors the system but it also fixes problems automatically.
While entity timers are associated with a specific entity bean and
primary key, stateless session bean timers are associated only with a
specific type of session bean. When a timer for a stateless session
bean goes off, the container selects an instance of that stateless
bean type from the instance pool and calls its ejbTimeout( )
method. This makes sense, because all
stateless session beans in the instance pool are logically
equivalent. Any instance can serve any client, including the
container itself.
Stateless session timers are often used to manage taskflow or when the timed event applies to a collection of entities instead of just one. For example, stateless session timers might be used to audit all maintenance records to ensure that they meet state and federal guidelines: at specific intervals, a timer notifies the bean to look up the maintenance records from all the Ships and generate a report. In contrast, a timer for an entity bean helps that entity manage its own state. A stateless session timer can also be used to do something like send notifications to all the passengers for a particular cruise, thus avoiding the timer attack problem.
The stateless session bean can access
the TimerService
from the
SessionContext
in the ejbCreate( )
, ejbRemove( )
, or any business method,
but it cannot access the timer service from the
setSessionContext( )
method. This means a client
must call some method on a stateless session bean (either create, or
a business method) in order for a timer to be set. This is the only
way to guarantee that the timer is set.
Setting a timer on the ejbCreate( )
method is problematic. First, there is no guarantee that
ejbCreate( )
will ever be called. The
ejbCreate( )
method’s stateless
session bean is called sometime after the bean is instantiated,
before it enters the Method Ready Pool. However, a container might
not create a pool of instances until the first client accesses that
bean, so if a client (remote or otherwise) never attempts to access
the bean, ejbCreate( )
may never be called and the
timer will never be set. Another problem with using
ejbCreate( )
is that it’s called
on every instance before it enters the pool; you have to prevent
subsequent instances (instances created after the first instance)
from setting the timer—the first instance created would have
already done this. It’s tempting to use a static
variable to avoid recreating timers (below), but it can cause
problems.
public class StatelessTimerBean implements javax.ejb.SessionBean, javax.ejb.TimedObject { static boolean isTimerSet = false; public void ejbCreate( ){ if( isTimerSet == false) { TimerService timerService = ejbContext.getTimerService( ); InitialContext jndiContext = new InitialContext( ); Long expirationDate = (Long) jndiContext.lookup("java:comp/env/expirationDate"); timerService.createTimer(expirationDate.longValue( ), null ); isTimerSet = true; } }
While this may seem like a good solution, it only works when your
application is deployed within a single server with one VM and one
classloader. If you are using a clustered system, a single server
with multiple VMs, or multiple classloaders (very common), it
won’t work because bean instances that are not
instantiated in the same VM with the same classloader will not have
access to the same static variable. In this scenario,
it’s easy to end up with multiple timers doing the
same thing. An alternative is to have ejbCreate( )
access and remove all preexisting timers to see if the timer is
already established, but this can affect performance because
it’s likely that new instances will be created and
added to the pool many times, resulting in many calls to
ejbCreate( )
and therefore many calls to
TimerService.getTimers( )
. Also, there is no
requirement that the timer service work across a cluster, so timers
set on one node in a cluster may not be visible to timers set on some
other node in the cluster.
With stateless session beans, you should
never use the ejbRemove( )
method to cancel or
create timers. The ejbRemove( )
method is called
on individual instances before they are evicted from memory. It is
not called in response to client calls to the remote or local remove
method. Also, the ejbRemove( )
method
doesn’t correspond to an un-deployment of a bean;
it’s only specific to a single instance. As a
result, you cannot determine anything meaningful about the EJB as a
whole from a call to the ejbRemove( )
method and
you should not use it to create or cancel timers.
When a stateless session bean implements the
javax.ejb.TimedObject
interface, its life cycle
changes to include the servicing of timed events. The Timer Service
pulls an instance of the bean from the instance pool when a timer
expires; if there are no instances in the pool, the container creates
one. Figure 13-2 shows the life cycle of a stateless
session bean that implements the TimedOut
interface.
The InactiveCustomer EJB is a stateless session timer that periodically cleans inactive customer records from the database. It activates every 30 days and deletes records for customers who were created between 4 and 5 months ago, and who have never booked a cruise. Titan discovered that it accumulated a lot of these inactive customer records because customers would occasionally book a cruise, then cancel the reservation, and never return for more business. Once it’s deployed and activated, the InactiveCustomer EJB continues to work automatically until canceled. It’s a schedule-and-forget-it type of agent. Here’s the bean class definition for the InactiveCustomer EJB:
public class InactiveCustomerBean implements javax.ejb.SessionBean,javax.ejb.TimedObject
{ final long THIRTY_DAYS = 1000 * 60 * 60 * 24 * 30;// Thirty Days in Milliseconds SessionContext ejbContext; public void setSessionContext(javax.ejb.SessionContext cntx){ ejbContext = cntx; } public void ejbCreate( ){}public void schedule(Date begin){
TimerService timerService = ejbContext.getTimerService( ); TimerService.createTimer(begin, THIRTY_DAYS);}
public void ejbTimeout( ) throws EJBException {
try{ Calendar calendar = Calendar.getInstance( ); calendar.add(Calendar.DATE, -120); Date date_120daysAgo = calendar.getTime( ); calendar = Calendar.getInstance( ); calendar.add(Calendar.DATE, -150); Date date_150daysAgo = calendar.getTime( ); InitialContext jndiEnc = new InitialContext( ); CustomerHomeLocal home = (CustomerHomeLocal) jndiEnc.lookup("java:comp/env/ejb/CustomerHomeLocal"); Iterator customers = home.findCustomersWithNoReservations( ).iterator( ); while(customers.hasNext( )){ CustomerLocal customer = (CustomerLocal)customers.next( ); Date dateCreated = customer.getDateCreated( ) if( dateCreated.after(date_150daysAgo) && dateCreated.before(date_120daysAgo)){ customer.remove( ); } } }catch(Exception e){ // exception handle logic goes here } } public void ejbActivate( ){} public void ejbPassivate( ){} public void ejbRemove( ){} }
The InactiveCustomer EJB has a single business method,
schedule( )
, that starts the timer, setting it to
expire first on the given date, and every 30 days thereafter. In
order for the InactiveCustomer EJB to function, a client application
must call schedule( )
and pass in a start date.
It’s probably best to develop a strategy similar to
this one, in which the timer is scheduled only after an explicit call
is made by a client, rather than attempting to design a stateless
session bean timer that somehow automatically schedules itself when
it’s deployed. The following code shows how an
InvalidCustomer EJB is scheduled by a client:
InvalidCustomerHomeRemote invalidCustomer = jndiEnc.lookup("java:comp/env/ejb/InvalidCustomerHomeRemote"); invalidCustomer.schedule( new Date( ) );
The ejbTimeout( )
method is hardcoded to fetch all
the customers created between four and five months ago who never made
reservations. These customers are removed from the system.
There are a number of improvements that could be made to this
strategy. For example, the time window could be configured in the
deployment descriptor or passed into the bean by the client. In
addition, the schedule( )
method should remove any
existing timers so that we don’t schedule multiple
timers for the same task. These types of changes are left as an
exercise for you to develop if you are interested.