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
The default dispatcher, based upon a
ScheduledThreadPoolExecutor
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:One of the methodsimport 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.get
Event
Context().set("supportEmail", supportEmail); Contexts.get
Event
Context().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.get
Event
Context().set("emailUser", emailUser); Contexts.get
Event
Context().set("oneTimePassword", oneTimePassword); renderer.render("/generic/email-template/onetimepassword-email-template.xhtml"); } }
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.get
Event
Context().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.