Sending Email with Spring


Many applications make use of electronic mail. Possible uses include sending content of a contact form and sending periodic reports about application usage and performance to an administrator. Spring facilitates sending of email through the MailSender interface, offering two different implementations — one based on JavaMail, and one using Jason Hunter's MailMessage class (included in the com.oreilly.servlet package).

Important 

The JavaMail API in particular is hard to mock or stub, partly because of its use of static methods and final classes. This and the heavy dependency on (system) properties make it hard to test application code that uses the JavaMail API directly. The abstraction layer provided by the Spring Framework allows easy mocking and testing.

The Spring mail framework is centered around the MailSender interface. This interface provides the abstraction over the underlying mailing system implementation. It allows for sending of Simple MailMessage instances. This allows for sending mail messages using the default mail message properties such as from, to, cc, bcc, subject, and text. For more sophisticated email messages, the two implementations provide support for MIME messages.

The complete MailSender interface is shown here:

public interface MailSender {       void send(SimpleMailMessage simpleMessage) throws MailException;           void send(SimpleMailMessage[] simpleMessages) throws MailException;     }

SimpleMailMessage is a JavaBean that holds what should be sent, and to whom.

Getting Started

To get started, consider a simple example of wiring up a mail sender in a Spring application context:

 <bean  >   <property name="host"><value>mail.mycompany.com</value></property> </bean> 

The preceding bean definition enables sending email from anywhere in your application, by means of the JavaMail implementation. Application code that would send an email using this mail sender would look like the following:

 package org.springframework.prospring.mail;      import org.springframework.mail.MailSender; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.MailException;      public class BusinessLogicImpl implements BusinessLogic {     private MailSender mailSender;          public void setMailSender(MailSender mailSender) {         this.mailSender = mailSender;     }          public void execute(Order order) {         SimpleMailMessage msg = new SimpleMailMessage();         msg.setFrom(me@mycompany.com);         msg.setTo("to@mycompany.com");         msg.setText("Hello, just sending you this message.");              try {             mailSender.send(msg);         } catch(MailException ex) {             //log it and go on             System.err.println(ex.getMessage());         }     } } 

The preceding code snippet shows how to send a simple message using the mail sender defined in the application context. To wire up the mail sender to the business logic, you will simply use Dependency Injection, as follows:

 <bean        >     <property name="mailSender"><ref bean="mailSender"></property> </bean> 

Let's now move on to more detailed examples, including a working mail implementation, using Velocity for email templating.

Reusing an Existing Mail Session

Although it's very useful while testing or running outside of a container, specifying the mail sending details in the application context can be a nuisance because the host and other properties may change on deployment. When running an application inside a container that already provides a mail session, the reuse of that session is preferred. Consider, for instance, running inside a container that exposes a JavaMail session through JNDI under the JNDI name java:/Mail. In this case the bean definition for the mail sender looks like this:

 <bean  >     <property name="session"><ref bean="mailSession"/></property> </bean>      <bean  >     <property name="jndiName"><value>java:/Mail</value></property> </bean> 

The JndiObjectFactoryBean is used to look up the mail session through JNDI. The retrieved mail session is set as the session in the mail sender. This approach enables the reuse of one mail session for all applications running in the container and therefore allows for a single point of configuration of the mail session.

Mail Sending Using COS

Spring also provides a mail implementation based on the MailMessage class included in COS. This is a very basic implementation of the MailSender interface. It should be used only when there are very simple mailing requirements: for example, no attachments or HTML content. We can change the example from the Getting started example earlier in this chapter to use COS mail to send the message as follows:

 <bean  >   <property name="host"><value>mail.mycompany.com</value></property> </bean> 

Only the class property of the mail sender bean definition needs to be modified. Note that the CosMail SenderImpl class does not support the replyTo and sentDate properties of the SimpleMailMessage. Specifying them will result in an exception being thrown when trying to send the message.

Generic Mail Manager

The remainder of this chapter combines the features of the Spring mail framework and provides a useful working example of a generic mail manager. The mail manager will use Velocity templates to allow for flexible electronic mail functionality. More information on Velocity can be found at http://jakarta.apache.org/velocity. Velocity is a popular choice for generating mail messages, as it can incorporate variables in its templates.

We start by defining a MailManager interface, which defines the business methods needed for a mail manager. The main value add here over Spring's MailSender interface — which of course is used under the covers — is Velocity-based mail templating support:

 package org.springframework.prospring.mail;      public interface MailManager {     void send(String senderName, String senderAddress,               Map from, Map to, Map cc, Map bcc,                String subject, Map mergeObjects, String template)           throws MailException;     void send(Map to, List objects, String template)          throws MailException; } 

Notice that each argument denoting a type of recipient is in the form of a Map. This is to allow the user to send an electronic mail message with multiple recipients for each recipient type, while specifying a full name for the recipient alongside the email address. Also note the convenience method to send a message while specifying only the "to" recipient.

The next step is to create a default implementation for our generic mail manager. We start out by setting up the skeleton for the default implementation:

 package.org.springframework.prospring.mail;      // imports...      public class MailManagerImpl implements MailManager {          private JavaMailSender javaMailSender;     private Resource templateBase;     private VelocityEngine velocityEngine;          private String defaultFromName;     private String defaultFromAddress;          private String textTemplateSuffix = "text.vm";     private String htmlTemplateSuffix = "html.vm";          private String overrideToAddress;          // setters (and optionally getters) for all private instance fields          public void send(Map to, List objects, String template)     throws MailException {         send(null, to, null, null, objects, template);     }          public void send(String senderName,                      String senderAddress,                      Map to,                       Map cc,                       Map bcc,                      String subject,                       Map mergeObjects,                       String templateLocation)      throws MailException {         // implementation of the functionality goes here     } } 

Note that in the preceding default implementation, all setters and getters are left out of the listing, as well as the implementation, which will be discussed next. Furthermore, note the override default name and default addresses and names for the "from" and "to" recipients. These will also be discussed in the implementation of the main send method. Also, the templateBase resource is discussed later. The JavaMailSender and VelocityEngine are assumed wired up in the application context.

Message Creation

After setting up the skeleton for the implementation of the generic mail manager, it is time to generate a message. The Spring framework provides a helper class for easy implementation of a MIME message. So the first part of the send method implementation creates a message using the mail sender and uses that message to create a helper for it.

 // create a mime message using the mail sender implementation MimeMessage message = javaMailSender.createMimeMessage();      // create the message using the specified template MimeMessageHelper helper; try {     helper = new MimeMessageHelper(message, true, "UTF-8"); } catch(MessagingException e) {     throw new MailPreparationException(             "unable to create the mime message helper", e); } 

Recipients

Next, the handling of the recipients must be implemented. A basic implementation looks like the following:

 // add the 'from’  // add the sender to the message helper.setFrom(senderAddress, senderName);      // add the 'cc’  if(cc != null && !cc.isEmpty()) {     Iterator it = cc.keySet().iterator();     while(it.hasNext()) {         String name = (String) it.next();         String recipient = (String) cc.get(name);         if(overrideToAddress != null) {             recipient = defaultToAddress;         }         helper.addCc(recipient, name);     } }      // add the 'bcc’  if(bcc != null && !bcc.isEmpty()) {     Iterator it = bcc.keySet().iterator();     while(it.hasNext()) {         String name = (String) it.next();         String recipient = (String) bcc.get(name);         if(overrideToAddress != null) {             recipient = defaultToAddress;         }         helper.addBcc(recipient, name);     } }      // add the 'to’  if(to != null && !to.isEmpty()) {     Iterator it = to.keySet().iterator();     while(it.hasNext()) {         String name = (String) it.next();         String recipient = (String) to.get(name);         if(overrideToAddress != null) {             recipient = defaultToAddress;         }         helper.addTo(recipient, name);     } } else {     // use the default 'to’     if(defaultToAddress != null) {         helper.addTo(defaultToAddress, toName);     } else {         helper.addTo(toAddress, toName);     } } 

Note that the code shown here performs little error handling or sanity checking. In the full example source for this chapter, a more extensive implementation is provided. Also note the special handling of the to, cc, and bcc recipients. In case the overrideToAddress has been specified, all recipients' email addresses are replaced with that address. This allows for easy testing and staging configuration of the mail manager, i.e. sending all mail messages to a specific test address. Note also that only one "from" recipient is allowed to be set; specifying more than one results in the last overwriting the others.

Content

The next step is to set the content of the mail message. We use Velocity to merge the list of objects into the specified template. The result is set as the content of the message.

 StringWriter text = new StringWriter(); VelocityEngineUtils.mergeTemplate(   velocityEngine, templateLocation + "/" + htmlTemplateSuffix,    mergeObjects, text);      String html = new StringWriter(); VelocityEngineUtils.mergeTemplate(   velocityEngine, templateLocation + "/" + htmlTemplateSuffix,    mergeObjects, text);      text.close(); html.close();      helper.setText(text.toString(), html.toString());      helper.setSubject(subject); 

Note the use of a base resource for our implementation of the message helper. Depending on the application deployment environment, different resources can be chosen for retrieving the Velocity templates. For instance, a ClassPathResource can be used when templates are not subject to runtime changes. With different needs, a FileSystemResource can be configured to load templates at runtime from disk. Velocity provides the means to cache the templates and handles modifications to the templates to be incorporated without the need to concern oneself with the specifics.

Attachments

In order for our generic mail manager to allow inline images and other attachments, we expand our mail manager by allowing extra files in a folder relative to the template directory to be added as (inline) attachments for the message. The added (inline) attachments are given the filename as the ID for referencing the attachment from the message content. The code snippet that follows implements this functionality:

 // retrieve contents of the 'attachments’ File attachmentsFolder = templateBase.createRelative(         "mail/" + template + "/attachment").getFile(); File[] attachments = attachmentsFolder.listFiles(); if(attachments != null) {     for(int i = 0; i < attachments.length; i++) {         // add the attachment to the message helper         helper.addAttachment(attachments[i].getName(),                  new FileDataSource(attachments[i]));     } }      // retrieve contents of the 'inline’  File inlinesFolder = templateBase.createRelative(         "mail/" + template + "/inline").getFile(); File[] inlines = inlinesFolder.listFiles(); if(inlines != null) {     for(int i = 0; i < inlines.length; i++) {         // add the attachment to the message helper         helper.addAttachment(inlines[i].getName(),                  new FileDataSource(inlines[i]));     } } 

Sending

Now that the message is created, its content is set, recipients are handled, and attachments are added, the message is ready to be sent. Using the MailSender wired up to the mail manager implementation, sending the message is easy. Just call the send method with the created and configured mail message as the argument.

 mailSender.send(message); 

Note that sending a message by means of the underlying messaging system may take some time to process. A connection to the mail server needs to be established, a handshake protocol needs to be executed, and the message content needs to be sent to the mail server. Depending on application needs, the sending of the mail message can be performed on a different thread. The main advantage of this approach is the client perceives no latency while sending a message. The main disadvantage, however, is that the client will not receive any error messages while sending messages. Of course these messages can be communicated to the client asynchronously, but this may pose some serious challenges. Again, choose the method for sending based on the application requirements.

This concludes the example of creating a generic mail manager by means of the mail functionality provided by the Spring framework. The created mail manager can be wired up in the application context of any application to add electronic mail as an option to communicate with clients. More information and a more detailed listing of the source code discussed in this chapter are provided on the website accompanying this book.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net