ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Creating Email Templates with XML

by Rafe Colburn
07/09/2003

One feature that seems to eventually creep into every web application is the ability to send email. Generally, it's a very specific kind of email, like a password reminder, welcome message, order confirmation, or receipt. Despite the fact that the content of these emails differs from application to application, the process of sending email rarely changes. You construct a message, give it to the mail server, and it gets delivered.

When you program in Java, the JavaMail API is most commonly used to do the dirty work of connecting to the mail server and sending the message. Unfortunately, it's kind of ponderous to use (mainly due to the flexibility of email), so if you're going to use it more than once, it generally makes sense to write a wrapper for it. Depending on how you use it, the wrapper may be written to send one specific kind of email, such as a password reminder, or to work generically, accepting the subject, recipients, and body of the email as arguments.

Once you have a wrapper for actually sending mail, you need a system for constructing the messages themselves. Let's look at password reminders. Nearly all email messages have a subject, body, and recipients. When a password reminder is sent, the user's email address and password are usually fetched from some sort of repository where login information is stored. The subject and the body have to be merged with the data in the database, and have to be stored somewhere, as well. One of the great problems of application design in general is where to keep these sorts of strings. In many cases, such strings are stored in property files, which keeps them from littering your source code and makes localization easier. I've used this approach for storing templates for outgoing emails in many web applications, but unfortunately, it's quite flawed.

Here's a quick list of reasons why property files are suboptimal for storing strings to be used in email messages:

  • Property files map to a very simple data structure — name and value pairs. They're not appropriate in cases where you want to have multiple values associated with a single key. For example, an email message might have four people in the To: field and three in the Cc: field. You can't easily do that using property files.
  • The formatting of property files is very strict. The name and value pair must be on a single line, so long strings can be a pain to deal with when you're editing the file. For example, it can be really painful to put the full body of an email message into one property. If you want to include line breaks within a property value, you have to encode them with \n.

One alternative approach is to use XML for the email templates, and that's the approach I'm going to discuss in this article. XML provides great flexibility in how you structure your templates and does not have the same strict formatting rules as property files do, so it's easier to maintain large strings. The main downside is that XML files can be trickier to deal with than property files. With a property file, it's easy to load the files and it's easy to access the properties once they've been loaded. On the other hand, it takes more work to load the XML file and process it using one of the many XML processing libraries provided for use with Java.

Related Articles:

Using the Jakarta Commons, Part 2 -- Ever find yourself thinking, "Someone's surely solved this problem before?" The Jakarta Commons is a good place to start looking when that thought tickles your mind. In this second of three articles, Vikram Goyal explores the Packages and XML groups of the Commons.

Learning and Using Jakarta Digester -- Turning an XML doc into Java bean objects is a common task, but the SAX and DOM APIs are too low-level. Jakarta Digester uses a series of rules to simplify this important task.

This article and the accompanying code attempt to take as much pain as possible out of the process for you by providing a generic framework that enables you to create email templates using XML files and send them. In this framework, I'll be using the Commons Digester package from the Jakarta project for XML processing, and the JavaMail API do send the actual email.

The Email Templates

Let's look at the format for the email template itself. The templates are XML files that contain only a root element and children of that element. The root element is <email>. The required children are <subject>, <body>, and <from>. The optional elements are <to>, <cc>, and <bcc>. If you've used an email client at all, you can deduce what each of these elements should contain. There can be multiple instances of the optional elements, so that you can include multiple addresses for each type of recipient. I'll explain how this works when I describe how the message is processed. Here's an example template file:

<email> 
    <from>rafe@rafe.us</from> 
    <to>someone@example.com</to> 
    <cc>someoneelse@example.com</cc> 
    <bcc>rafe@rafe.us</bcc> 
    <subject>This is the subject</subject> 
    <body>This is the body of an email message.</body> 
</email>

Customizable Templates

One useful feature provided by property files is that you can use the MessageFormat class to substitute placeholders in your properties with values that are passed in at runtime. For example, if you are putting errors in a property file and one of the errors is file not found, you can write the property like this:

file.not.found.error=Error, could not find file {0}.

Then, at runtime, you can use MessageFormat as follows:

ResourceBundle bundle = ResourceBundle.getBundle(
	"MyProperties", currentLocale); 
 
Object[] arguments    = { "some_file.txt" }; 
 
String newString      = MessageFormat.format(
	bundle.getString("file.not.found.error"), arguments);

At the end, newString will contain Error, could not find file some_file.txt. I've built similar flexibility into this system. The MessageFormat can format any string, so you can embed the same tokens that can be used in a property file into the subject and body elements of your email template.

In some cases, you'll want to insert personalized information into your templates when the email is sent. For example, you might want to include the recipient's first name in the email, or perhaps the details of their order. This system handles these situations by processing the body and subject of the email template using MessageFormat. The catch is that it accepts only one array of arguments that are applied to both the subject and the body. So the subject could contain tokens {0}, {2}, and {3}, and the body could contain tokens {0}, {1}, and {4}. I took this approach because in many cases, the same arguments are used in both the body and the subject and it simplifies the parameter list that's passed in to the EmailSender.

Processing the Template

Once you've created your template, the next step is to process it. As you probably know, there are many XML processing libraries to choose from. Commons Digester, part of the Jakarta Commons project, was originally created as part of the Struts project in order to provide a quick and easy way to parse the Struts configuration file. It provides an easy way to map the elements in the XML file to a data structure using syntax similar to XPath. The advantage is that it enables you to pluck needed elements out of an XML document without parsing it node-by-node using SAX or dealing with the treelike data structure provided by the DOM.

Here's the method that reads the data from the XML file and copies it into an EmailTemplate object:

public static EmailTemplate getEmailTemplate(InputStream aStream) 
{ 
    Digester digester = new Digester(); 
    digester.setValidating(false); 
 
    digester.addObjectCreate("email", EmailTemplate.class); 
 
    digester.addBeanPropertySetter("email/subject", "subject"); 
    digester.addBeanPropertySetter("email/body", "body"); 
    digester.addBeanPropertySetter("email/from", "from"); 
    digester.addCallMethod("email/to", "addTo", 0); 
    digester.addCallMethod("email/cc", "addCc", 0); 
    digester.addCallMethod("email/bcc", "addBcc", 0); 
 
    try 
    { 
        return (EmailTemplate)digester.parse(aStream); 
    } 
    catch (IOException e) 
    { 
        logger.error("Error: ", e); 
        return null; 
    } 
    catch (SAXException e) 
    { 
        logger.error("Error: ", e); 
        return null; 
    } 
}

Let's look at the example line by line. The way Commons Digester works is that you set up some rules to apply to the file to be parsed. Before I set up my processing rules, I set the validating flag on the digester to false, because there's no DTD to validate the email template against. To begin processing the file, I instantiate the Digester object and then call methods to set up the data mapping rules. First, I call the addObjectCreate() method, which sets up a rule to create an EmailTemplate object whenever an email element is encountered. email is the root element in the XML template file, so each file maps to one EmailTemplate object.

For elements that only appear once in the template file, I use the addBeanPropertySetter() method. It accepts two arguments, the path to an element to match and the setter to call. In the first call, I indicate that the values in elements that match the pattern email/subject should be passed in to the subject setter of the EmailTemplate class. The patterns describe the nesting order of elements to search for, separated by slashes. In this case, the pattern matches subject elements that are children of email elements. Wildcards can also be used in patterns to provide more flexibility. For a full description of how patterns are constructed, see the JavaDoc for Commons Digester.

For elements that can occur multiple times in the template file, calling the setter method for the property won't work. Instead, I use the addCallMethod() rule, which takes the value in the element and calls the specified method. I use the version of this method that takes three arguments: the pattern to match, the method to call, and the number of arguments the method being called takes. In all three cases, I pass in 0 as the third argument, which indicates that the body of the matched element is the only argument that should be passed to the method being called. In the EmailTemplate class, I've written three methods, addTo(), addCc(), and addBcc(), which add the recipients listed in the template file to collections in the template class.

Once the rules have been set up for the six types of children of the email element, I'm ready to parse the file. In this case, I pass in the InputStream that the getEmailTemplate method accepts as an argument. The parse method can accept a File, a SAX InputSource, an InputStream, a Reader, or a URI as the target for the file to parse. I chose an InputStream in this case. It's up to the calling code to fetch the XML file and turn it into an InputStream. To write this in a more generic way, I could have set up this method to accept an Object and used the instanceof operator inside to verify that the argument is one of the types of arguments accepted by the parse method and then call it appropriately.

The parse method can throw an IOException or a SAXException. Those are handled by passing the exception off to a Log4J logger, and then returning null. If no exceptions occur, then the new EmailTemplate object generated by the Digester is returned.

The Rest of the EmailTemplate Class

The meat of the EmailTemplate class is the getEmailTemplate() method. The rest is just properties and convenience methods. It has three String properties: the body, subject, and from address, and three ArrayList properties: the to, CC, and BCC lists, all of which will contain strings as members. There are getters and setters for those properties, as well as "add" methods for the collections. There are three additional convenience methods: getToAddresses(), getCcAddresses(), and getBccAddresses(). The JavaMail API expects that you'll pass in addresses as old-fashioned arrays of InternetAddress objects. These methods convert the ArrayLists of String objects into the arrays required by JavaMail.

The EmailSender

Once the template file is parsed and is an EmailTemplate object, the next step is to send the email message. The EmailSender class includes one static, overloaded method — sendEmail(). It can be called in a number of different ways, all of which are shortcuts to the full method signature:

public static void sendEmail( 
    String aTo, 
    EmailTemplate aTemplate, 
    String[] aArgs)

The arguments don't require much explanation. The first is a To address for the email. You can include recipient addresses in your email templates, but in many cases, the system will need to specify a recipient at runtime. For example, if you're sending a password reminder, it needs to go to the email address of the user requesting the password. The recipient addresses in the email template are for cases where the system needs to send an email to a hardcoded address or needs to include certain hardcoded recipients for testing. For example, suppose a system needs to generate an email that triggers a workflow every time an order is submitted — the hardcoded address in the template is useful in those situations.

The second argument is the EmailTemplate itself. The third is the list of arguments that will be passed to MessageFormat when it parses the subject and body of the email. It's up to the calling code to create the array of information used to personalize the email template. There are also several other method declarations that simplify calling this method (so that you can call it without specifying a recipient, or without arguments).

The body of the sendEmail() method mostly consists of calls to set up sending the message using JavaMail. I talked about the fact that there was a lot of overhead when it comes to using JavaMail, and now you see what I meant. First, I check to make sure that the EmailTemplate isn't null. If it is, there's nothing I can do. The first step in setting up is creating a Properties object (a glorified Hashtable) with the SMTP server set. In my configuration, I have the SMTP server setting in a properties file, so I read it from the properties file and put it in the properties object I created.

I then create a JavaMail Session object and pass in the Properties object. The Session object is needed to create the MimeMessage object, which I do next. Then I set the From: address to the address specified in the EmailTemplate object that I passed in as an argument. The next step is to set the To: field for the message I'm constructing. There are some gymnastics here because the user can pass in a To: address and the template can also contain any number of To: addresses. The problem here is that JavaMail likes to use arrays for lists of addresses, so it's up to me to determine how large the list of recipients is and construct the array to pass in. Once I've created an array of recipients that includes the argument to the method (if it's not null) and the recipients in the template, I set it on the MimeMessage object.

Since all CC: and BCC: addresses must be specified within the template, handling them is more straightforward. I simply use the convenience methods in the EmailTemplate class and add the additional recipients to the message. As I mentioned earlier, I use MessageFormat to apply any arguments supplied with the method call to the subject and body. Once I've done so, I copy the new subject into the message object. I do the same thing with the message body. All that's left at that point is to call Transport.send() and pass in the MimeMessage object.

Using the System

Now that I've explained how the system works, I'll explain how to use it from a servlet, though it will work similarly in any real program. Here's the code:

// Grab the email template. 
InputStream template = 
    getServlet() 
        .getServletConfig() 
        .getServletContext() 
        .getResourceAsStream( 
            "/WEB-INF/email/registrationNotification.xml"); 
 
EmailTemplate notification = EmailTemplate.getEmailTemplate(template); 
 
// Create the section of the email containing the actual user data. 
String[] args = { "Rafe" }; 
 
EmailSender.sendEmail("rafe@rafe.us", notification, args);

The first step in using this system is to turn your XML template file into an InputStream. Because I'm using a servlet, I fetch the file from the ServletContext. There are other ways to get the file, but in a servlet environment, this one works well. Then all I have to do is pass the InputStream off to the EmailTemplate.getEmailTemplate() method that I described earlier. Next, I just set up my array of arguments for customizing the email and call the EmailSender.sendEmail() method.

Going Further

There are a number of enhancements that can be made to this system. Two obvious improvements are the ability to support both HTML and plaintext email, and the ability to include attachments with the email messages. To create these kinds of messages, the javax.mail.MimeMultipart message type is used. There's also the issue of where to store and how to specify attachments. In my system, I handle adding the attachments outside of the context of the template file, because my attachments are generated at the time the email is sent.

Rafe Colburn is a Java developer and computer book author, and has written on Perl, CGI programming, HTML, and Java.


Return to ONJava.com.