This blog is mainly about Java...

Monday, April 5, 2010

Advanced Seam series part 3 of 3: Asynchronous mail sending

Advanced Seam series part 3 of 3: Asynchronous mail sending

In this last series I will be showing how easy you can set up your seam environment to handle asynchronous mail sending. You can even raise other events asynchronously.

Seam makes it very easy to perform work asynchronously from a web request.

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
Asynchronous configuration
The default dispatcher, based upon aScheduledThreadPoolExecutor performs efficiently but does not guarantee that the task will ever actually be executed. This is because it does not have a persistence state, meaning it only stores the events in memory. If your application server goes down in between the calls, they will not run.
If you have a task that is not critical that it must be performed, ie a clean up task, or something trivial, then it is no reason not to use the default dispatcher.

However, if you want the guarantee that the task is called you might use the Timer Service.
If you're working in an environment that supports EJB 3.0, you can 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. The Timer Service implementation is persistence based, thus you will get some guarantee that the tasks will eventually run.

Finally, your third is to use an Open Source alternative called Quartz (recently acquired by Terracotta). To use Quartz, you will 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/>
Note that Quartz uses RAMJobStore as default, thus it is not persistence based on default. You will need to configure it to use persistence base.
It is up to the reader to choose whichever asynchronous strategy they see fit.

Sending emails Asynchronously
It is really very easy sending a plain email with Seam. All you have to do is use Seam mail, annotate the method and interface (if you are using EJB's) with @Asynchronous and its done. However, the tricky part is using EL expressions and variables stored in the different Contexts, so that you can produce dynamic emails.

There are two ways you can call a task asynchronously. You can either send an asynchronous event, or as I previously explained, annotate a method with @Asynchronous, and call it normally, ie:
Events.instance().raiseAsynchronousEvent("sendOneTimepassword", user, theOnetimepass);
//Or the normal way
@In MailService mailService;
mailService.sendSupport(sender,supportEmail,supportMessage);
Lets say you have the following Stateless EJB that is responsible for sending emails:

import javax.ejb.Local;
import javax.ejb.Stateless;

import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.async.Asynchronous;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.faces.Renderer;

/**
 * This service is responsible for sending emails asynchronously
 * @author Shervin Asgari
 *
 */
@Stateless
@Local(MailService.class)
@Name("mailService")
@AutoCreate
public class MailServiceImpl implements MailService {

    @In(create = true)
    private Renderer renderer;
    
    @Asynchronous
    public void sendSupport(User sender, String supportEmail, String supportMessage) {
        Contexts.getEventContext().set("sender", sender);
        Contexts.getEventContext().set("supportEmail", supportEmail);
        Contexts.getEventContext().set("supportMessage", supportMessage);
        renderer.render("/generic/email-template/support-email-template.xhtml");
    }
    
    @Observer("sendOneTimepassword")
    //@Asynchronous, we dont need to annotate with @Asynchronous if we are raising the event asynchronously
    public void sendOneTimepassword(User emailUser, String oneTimePassword) {
        Contexts.getEventContext().set("emailUser", emailUser);
        Contexts.getEventContext().set("oneTimePassword", oneTimePassword);
        renderer.render("/generic/email-template/onetimepassword-email-template.xhtml");
    }    
}
One of the methods sendSupport() is responsible for sending support messages as email, whilst the other method sendOneTimepassword is used to send generated one time passwords to a user so that they can authenticate to the system. Have you look here for information on how you can set up your system to do exactly that.

The key point to note is the Contexts.getEventContext().set("emailUser", emailUser);. If I would have normally injected the user in the EmailService, it would not work. Since the call is made asynchronously we need to set the variables in the asynchronous context. Thus we can now get hold of them in the email template:


<m:message 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:m="http://jboss.com/products/seam/mail"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">

    <m:from name="Someone">no-reply@someplace.com</m:from>
    <m:to name="#{emailUser.name}" address="#{emailUser.fromEmail}" />
    <m:subject>#{messages['mail.onetime.heading']}</m:subject>
    <m:body>
        Your one time password is: <i>#{oneTimePassword}</i>
        <p>#{messages['mail.onetime.noreply']}</p>
    </m:body>
</m:message>
Thats really all there is to it! Now your emails will be sent asynchronously.

Labels