SeamFramework.orgCommunity Documentation

Chapter 23. Asynchronicity and messaging

23.1. Messaging in Seam
23.1.1. Configuration
23.1.2. Sending messages
23.1.3. Receiving messages using a message-driven bean
23.1.4. Receiving messages in the client
23.2. Asynchronicity
23.2.1. Asynchronous methods
23.2.2. Asynchronous methods with the Quartz Dispatcher
23.2.3. Asynchronous events
23.2.4. Handling exceptions from asynchronous calls

Seam makes it very easy to perform work asynchronously from a web request. When most people think of asynchronicity in Java EE, they think of using JMS. This is certainly one way to approach the problem in Seam, and is the right way when you have strict and well-defined quality of service requirements. Seam makes it easy to send and receive JMS messages using Seam components.

But for cases when you are simply want to use a worker thread, JMS is overkill. Seam layers a simple asynchronous method and event facility over your choice of dispatchers:

This chapter first covers how to leverage Seam to simplify JMS and then explains how to use the simpler asynchronous method and event facility.

Seam makes it easy to send and receive JMS messages to and from Seam components. Both the message publisher and the message receiver can be Seam components.

You'll first learn to setup a queue and topic message publisher and then look at an example that illustrates how to perform the message exchange.

You can process messages using any EJB 3 message-driven bean. The MDB can even be a Seam component, in which case it's possible to inject other event- and application- scoped Seam components. Here's an example of the payment receiver, which delegates to a payment processor.

First, create an MDB to receive the message.

@MessageDriven(activationConfig = {

    @ActivationConfigProperty(
        propertyName = "destinationType",
        propertyValue = "javax.jms.Queue"
    ),
    @ActivationConfigProperty(
        propertyName = "destination",
        propertyValue = "queue/paymentQueue"
    )
})
@Name("paymentReceiver")
public class PaymentReceiver implements MessageListener
{
   @Logger private Log log;
   @In(create = true) private PaymentProcessor paymentProcessor;
    
   @Override
   public void onMessage(Message message)
   {
      try
      {
         paymentProcessor.processPayment((Payment) ((ObjectMessage) message).getObject());
      } 
      catch (JMSException ex)
      {
         log.error("Message payload did not contain a Payment object", ex);
      } 
   }
}

Then, implement the Seam component to which the receiver delegates processing of the payment.

@Name("paymentProcessor")

public class PaymentProcessor
{
   @In private EntityManager entityManager;
   public void processPayment(Payment payment)
   {
      // perhaps do something more fancy
      entityManager.persist(payment);
   }
}

If you are going to be performing transaction operations in your MDB, you should ensure that you are working with an XA datasource. Otherwise, it won't be possible to rollback database changes if the database transaction commits and a subsequent operation being performed by the message fails.

Asynchronous events and method calls have the same quality of service expectations as the underlying dispatcher mechanism. The default dispatcher, based upon a ScheduledThreadPoolExecutor performs efficiently but provides no support for persistent asynchronous tasks, and hence no guarantee that a task will ever actually be executed. If you're working in an environment that supports EJB 3.0, and add the following line to components.xml:


<async:timer-service-dispatcher/>

then your asynchronous tasks will be processed by the container's EJB timer service. If you're not familiar with the Timer service, don't worry, you don't need to interact with it directly if you want to use asynchronous methods in Seam. The important thing to know is that any good EJB 3.0 implementation will have the option of using persistent timers, which gives some guarantee that the tasks will eventually be processed.

Another alternative is to use the open source Quartz library to manage asynchronous method. You need to bundle the Quartz library JAR (found in the lib directory) in your EAR and declare it as a Java module in application.xml. The Quartz dispatcher may be configured by adding a Quartz property file to the classpath. It must be named seam.quartz.properties. In addition, you need to add the following line to components.xml to install the Quartz dispatcher.


<async:quartz-dispatcher/>

The Seam API for the default ScheduledThreadPoolExecutor, the EJB3 Timer, and the Quartz Scheduler are largely the same. They can just "plug and play" by adding a line to components.xml.

In simplest form, an asynchronous call just lets a method call be processed asynchronously (in a different thread) from the caller. We usually use an asynchronous call when we want to return an immediate response to the client, and let some expensive work be processed in the background. This pattern works very well in applications which use AJAX, where the client can automatically poll the server for the result of the work.

For EJB components, we annotate the local interface to specify that a method is processed asynchronously.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public void processPayment(Payment payment);
}

(For JavaBean components we have to annotate the component implementation class.)

The use of asynchronicity is transparent to the bean class:

@Stateless

@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    public void processPayment(Payment payment)
    {
        //do some work!
    }
}

And also transparent to the client:

@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String pay()
    {
        paymentHandler.processPayment( new Payment(bill) );
        return "success";
    }
}

Note

Please distinguish between EJB 3.1 annotation javax.ejb.Asynchronous and Seam annotation org.jboss.seam.annotations.async.Asynchronous. While first is designated for session beans only, the latter works in non-EJB environment too.

The asynchronous method is processed in a completely new event context and does not have access to the session or conversation context state of the caller. However, the business process context is propagated.

Asynchronous method calls may be scheduled for later execution using the @Duration, @Expiration and @IntervalDuration annotations.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public void processScheduledPayment(Payment payment, @Expiration Date date);
    @Asynchronous
    public void processRecurringPayment(Payment payment, 
                                        @Expiration Date date, 
                                        @IntervalDuration Long interval)'
}
@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() );
        return "success";
    }
    public String scheduleRecurringPayment()
    {
        paymentHandler.processRecurringPayment( new Payment(bill), bill.getDueDate(), 
                                                ONE_MONTH );
        return "success";
    }
}

Both client and server may access the Timer object associated with the invocation. The Timer object shown below is the EJB3 timer when you use the EJB3 dispatcher. For the default ScheduledThreadPoolExecutor, the returned object is Future from the JDK. For the Quartz dispatcher, it returns QuartzTriggerHandle, which we will discuss in the next section.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public Timer processScheduledPayment(Payment payment, @Expiration Date date);
}
@Stateless

@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    @In Timer timer;
    
    public Timer processScheduledPayment(Payment payment, @Expiration Date date)
    {
        //do some work!
        
        return timer; //note that return value is completely ignored
    }
}
@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        Timer timer = paymentHandler.processScheduledPayment( new Payment(bill), 
                                                              bill.getDueDate() );
        return "success";
    }
}

Asynchronous methods cannot return any other value to the caller.

The Quartz dispatcher (see earlier on how to install it) allows you to use the @Asynchronous, @Duration, @Expiration, and @IntervalDuration annotations as above. But it has some powerful additional features. The Quartz dispatcher supports three new annotations.

The @FinalExpiration annotation specifies an end date for the recurring task. Note that you can inject the QuartzTriggerHandle.



        @In QuartzTriggerHandle timer;
        
    // Defines the method in the "processor" component
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalDuration Long interval,
                                 @FinalExpiration Date endDate, 
                                 Payment payment) 
    { 
        // do the repeating or long running task until endDate
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    // Starts now, repeats every hour, and ends on May 10th, 2010
    Calendar cal = Calendar.getInstance ();
    cal.set (2010, Calendar.MAY, 10);
    processor.schedulePayment(new Date(), 60*60*1000, cal.getTime(), payment);

Note that the method returns the QuartzTriggerHandle object, which you can use later to stop, pause, and resume the scheduler. The QuartzTriggerHandle object is serializable, so you can save it into the database if you need to keep it around for extended period of time.

QuartzTriggerHandle handle =

         processor.schedulePayment(payment.getPaymentDate(), 
                                   payment.getPaymentCron(), 
                                   payment);
        payment.setQuartzTriggerHandle( handle );
        // Save payment to DB
        
        // later ...
        
        // Retrieve payment from DB
        // Cancel the remaining scheduled tasks
        payment.getQuartzTriggerHandle().cancel();

The @IntervalCron annotation supports Unix cron job syntax for task scheduling. For instance, the following asynchronous method runs at 2:10pm and at 2:44pm every Wednesday in the month of March.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalCron String cron, 
                                 Payment payment) 
    { 
        // do the repeating or long running task
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), "0 10,44 14 ? 3 WED", payment);

The @IntervalBusinessDay annotation supports invocation on the "nth Business Day" scenario. For instance, the following asynchronous method runs at 14:00 on the 2nd business day of each month. By default, it excludes all weekends and US federal holidays until 2010 from the business days.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalBusinessDay NthBusinessDay nth, 
                                 Payment payment) 
    { 
        // do the repeating or long running task
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), 
          new NthBusinessDay(2, "14:00", WEEKLY), payment);

The NthBusinessDay object contains the configuration of the invocation trigger. You can specify more holidays (e.g., company holidays, non-US holidays etc.) via the additionalHolidays property.



public class NthBusinessDay implements Serializable
{
      int n;
      String fireAtTime;
      List <Date> additionalHolidays;
      BusinessDayIntervalType interval;
      boolean excludeWeekends;
      boolean excludeUsFederalHolidays;
      public enum BusinessDayIntervalType { WEEKLY, MONTHLY, YEARLY }
      public NthBusinessDay ()
      {
        n = 1;
        fireAtTime = "12:00";
        additionalHolidays = new ArrayList <Date> ();
        interval = BusinessDayIntervalType.WEEKLY;
        excludeWeekends = true;
        excludeUsFederalHolidays = true;
      }     
      ... ...
}

The @IntervalDuration, @IntervalCron, and @IntervalNthBusinessDay annotations are mutually exclusive. If they are used in the same method, a RuntimeException will be thrown.

Each asynchronous dispatcher behaves differently when an exception propagates through it. For example, the java.util.concurrent dispatcher will suspend further executions of a call which repeats, and the EJB3 timer service will swallow the exception. Seam therefore catches any exception which propagates out of the asynchronous call before it reaches the dispatcher.

By default, any exception which propagates out from an asynchronous execution will be caught and logged at error level. You can customize this behavior globally by overriding the org.jboss.seam.async.asynchronousExceptionHandler component:

@Scope(ScopeType.STATELESS)

@Name("org.jboss.seam.async.asynchronousExceptionHandler")
public class MyAsynchronousExceptionHandler extends AsynchronousExceptionHandler { 
   @Logger Log log;
   
   @In Future timer;
   
   @Override
   public void handleException(Exception exception) {
      log.debug(exception);
      timer.cancel(false);
   }
   
}

Here, for example, using java.util.concurrent dispatcher, we inject its control object and cancel all future invocations when an exception is encountered

You can also alter this behavior for an individual component by implementing the method public void handleAsynchronousException(Exception exception); on the component. For example:

   public void handleAsynchronousException(Exception exception) {

      log.fatal(exception);
   }