Chapter 21. Asynchronicity and messaging
Seam makes it easy to perform work asynchronously from a web request. Asynchronicity in Java EE is usually linked with JMS, and where your quality of service requirements are strict and well-defined, this is logical. It is easy to send JMS messages through Seam components.
However, for many use cases, JMS is more powerful than necessary. Seam layers a simple, asynchronous method and event facility over your choice of dispatchers:
java.util.concurrent.ScheduledThreadPoolExecutor
(by default)- the EJB timer service (for EJB 3.0 environments)
- Quartz
21.1. Asynchronicity
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 are working in an environment that supports EJB 3.0, add the following line to components.xml
to ensure that your asynchronous tasks are processed by the container's EJB timer service:
<async:timer-service-dispatcher/>
If you want to use asynchronous methods in Seam, you do not need to interact directly with the Timer service. However, it is important that your EJB3 implementation has the option of using persistent timers, which give some guarantee that the task will eventually be processed.
Alternatively, you can use the open source Quartz library to manage asynchronous method. To do so, bundle the Quartz library
JAR
(found in the lib
directory) in your EAR
, and declare it as a Java module in application.xml
. You can configure the Quartz dispatcher by adding a Quartz property file to the classpath —this file must be named seam.quartz.properties
. To install the Quartz dispatcher, you will also need to add the following line to components.xml
:
<async:quartz-dispatcher/>
Since the Seam API for the default
ScheduledThreadPoolExecutor
, the EJB3 Timer
, and the Quartz Scheduler
are very similar, you can "plug and play" by adding a line to components.xml
.
21.1.1. Asynchronous methods
An asynchronous call allows a method call to be processed asynchronously (in a different thread) to the caller. Usually, asynchronous calls are used when we want to send an immediate response to the client, and simultaneously process expensive work in the background. This pattern works well in AJAX applications, where the client can automatically poll the server for the result of the work.
For EJB components, annotate the implementation of the bean to specify that a method be processed asynchronously. For JavaBean components, annotate the component implementation class:
@Stateless @Name("paymentHandler") public class PaymentHandlerBean implements PaymentHandler { @Asynchronous public void processPayment(Payment payment) { //do some work! } }
Asynchronicity is transparent to the bean class. It is 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"; } }
The asynchronous method is processed in a fresh event context, and has no access to the session or conversation context state of the caller. However, the business process context is propagated.
You can schedule asynchronous method calls for delayed execution with 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 can access the
Timer
object associated with the invocation. The Timer
shown below is the EJB3 timer used with the EJB3 dispatcher. For the default ScheduledThreadPoolExecutor
, the timer returns Future
from the JDK. For the Quartz dispatcher, it returns QuartzTriggerHandle
, which will be discussed 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.
21.1.2. Asynchronous methods with the Quartz Dispatcher
The Quartz dispatcher lets you use the
@Asynchronous
, @Duration
, @Expiration
, and @IntervalDuration
annotations, as above, but it also supports several additional annotations.
The
@FinalExpiration
annotation specifies an end date for a 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 this method returns the
QuartzTriggerHandle
object, which can be used to stop, pause, and resume the scheduler. The QuartzTriggerHandle
object is serializable, so it can be saved into the database if required for an 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 example, 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 in the "nth Business Day" scenario. For instance, the following asynchronous method runs at 14:00 on the 2nd business day of each month. All weekends and US Federal holidays are excluded from the business days by default.
// 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 (company holidays and non-US holidays, for example) in 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. Attempting to use them in the same method will cause a RuntimeException
error.
21.1.3. Asynchronous events
Component-driven events can also be asynchronous. To raise an event for asynchronous processing, call the
raiseAsynchronousEvent()
method of the Events
class. To schedule a timed event, call the raisedTimedEvent()
method and pass a schedule object. (For the default dispatcher or timer service dispatcher, use TimerSchedule
.) Components can observe asynchronous events as usual, but only business process context is propagated to the asynchronous thread.
21.1.4. Handling exceptions from asynchronous calls
Each asynchronous dispatcher behaves differently when an exception propagates through it. For example, the
java.util.concurrent
suspends further executions of a repeating call, and the EJB3 timer service swallows the exception, so Seam catches any exception that propagates from the asynchronous call before it reaches the dispatcher.
By default, any exception that propagates 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, with
java.util.concurrent
dispatcher, we inject its control object and cancel all future invocations when an exception is encountered.
You can alter this behavior for an individual component by implementing the
public void handleAsynchronousException(Exception exception);
method on that component, like so:
public void handleAsynchronousException(Exception exception) { log.fatal(exception); }