Take the fast track to text generation

Save time and frustration with template engines like Velocity, WebMacro, and Turbine

Most programs need to create some sort of text output, like email messages, HTML files, or console output. But because computers speak only in binary, programmers must make their software create intelligible text. In this article, I show you how using template engines can save you time when creating text output. You will learn the advantages of templates and find out how to create effective templates in different scenarios. Say goodbye to System.println()!

While programmers can easily write code to output text messages (after all, that's the first thing you learn in the Hello World example), often they're not the best people for composing messages in the first place. That is why we assign the job to marketing and public relations departments. But message writers unfortunately depend on programmers to complete even the most mundane tasks in terms of program output, which leads to misunderstandings and frustration for both sides.

Take a simple example: a Java program collects some client information from a data source and sends an account statement via email to each of your company's customers. Look at a sample program that accomplishes this task (the full examples are available for download from Resources):

        for (int i=0; i < customers.size(); i++)
        {
            Customer customer = (Customer)customers.get(i);
            
            StringBuffer message = new StringBuffer();
            message.append ("Dear Mr/Mrs ");
            message.append (customer.getLastName());
            message.append ("\n");
            message.append ("\n");
            message.append ("The total on your account is ");
            message.append (customer.getAccountTotal());
            message.append ("\n");
            message.append ("\n");
            message.append ("Regards");
            message.append ("\n");
            message.append ("Widgets and Gadgets Inc.");
            
            // Send Email
            mm.sendMail (customer.getFirstName(), customer.getEmail(), "Account", message.toString());
        }

The example above provides one of the worst ways to send a message. By embedding the message into the program source code, you make it virtually impossible for any nonprogrammer to edit the message without a programmer's help. You also make editing difficult for any programmer who does not know the code. Perhaps, because you foresee this detriment, you write the code below:

    static public final String STR_HELLO="Dear Mr/Mrs ";
    static public final String STR_MESSAGE="The total on your account is ";
    static public final String STR_BEY="Regards\nWidgets and Gadgets Inc.";

The code above doesn't ease the situation much, if at all. A nonprogrammer can hardly be expected to understand the meaning of static or final. Furthermore, code like this is not flexible enough to handle a change in message structure. You might, for example, be required to add more information from the data model to the email message, which would require you to change the email build code and possibly add more static final String objects.

Introduction to templates

Loading the message from a text file would solve some of the problems -- except for the provision of dynamic content, which is the most important aspect of the system. You need a way to insert dynamic content into a prewritten text message. If you use one of the text-based template engines available, it will complete all the hard work for you.

Template engines solve the problem of inserting dynamic content into a text message. Instead of embedding your message code into your program, you create a simple text file that contains the text content. A template engine parses that text file, called a text template, and any dynamic content is inserted with the help of simple template directives. The Jakarta Project's Velocity is my template engine of choice, but you can use one of the many other available engines. Velocity and WebMacro are probably the two most feature-rich and popular engines out there. Both are freely available under open source licenses.

Although I use Velocity in my examples, you should be able to easily port the examples to a different template engine by adopting the relevant engine's syntax.

Let's look at the email example I completed using Velocity. You need to download Velocity and add it to your classpath for the example to compile and run. You also need JavaMail if you want the email to work.

        for (int i=0; i<customers.size(); i++)
        {
            Customer customer = (Customer)customers.get(i);
            
            // Create a context and add all the objects
            VelocityContext context = new VelocityContext();
            context.put ("lastname",customer.getLastName());
            context.put ("total", new Double (customer.getAccountTotal()));
            context.put ("customer", customer );
            
            // Render the template for the context into a string
            StringWriter message = new StringWriter();
            template.merge(context, message);
            
            // Send Email
            mm.sendMail (customer.getFirstName(), customer.getEmail(), "Account", message.toString());
        }

First, you must understand the Java source code above. Instead of generating text as the first example did, the class above renders a text file called a Velocity template and emails the result to its recipient. A Velocity template can be any text file, but normally it contains some directives to insert dynamic content. The most interesting part of the Java code is the Velocity context. The Velocity context provides the link between your Java code and the Velocity text template, which another person could write. Any object added to the context is available to the template by the name given in the put() method's first parameter. To illustrate how that works, here is the template file:

Dear Mr/Mrs $lastname
The total of your account statement is $total
Regards
Widgets and Gadgets Inc.

When Velocity renders a template, it echoes all the text in the file except for the text that starts with $. A dollar sign indicates a location where a value from an object added to the context should be placed in the template's rendered output. Because I have context.put("name",customer.getName()) in the Java class, when the template renders, the customer's name replaces any call to $name. The output of the added object's toString() method replaces the Velocity variables (those beginning with $).

One of the most powerful and frequently used features of a template engine is its ability to discover object information using a built-in reflection engine. The engine allows you to retrieve the value of any public method or any object's data member added to the context with a convenient Java-like dot notation. The engine also provides another improvement: a shorthand for JavaBean properties. When using objects with JavaBean properties, you can omit the get part of the method and the trailing brackets. (For complete details check the documentation of your favorite template engine.) See the template below for an example. Because I added the Customer object to the context in the previous example's Java code, I can rewrite the template like this:

Dear Mr/Mrs $customer.LastName
The total of your account statement is $customer.AccountTotal
Regards
Widgets and Gadgets Inc.

Advanced template engines like Velocity and WebMacro can do more than merely replace variables. They also have built-in directives for comparison and iteration. (Even though comparison and iteration provide common ground between template engines, their syntaxes differ just enough for the templates not to be fully compatible. Keep that in mind when choosing an engine or migrating between offerings.)

Say, for instance, in December your boss wants to add a Christmas greeting to all your program's emails. You could just add this message to the template and remove it again later, but that would require you to show up for work on New Year's Day to remove the Christmas message.

A better idea is to tell the template when to display the message. To do that, you first add the current month to the Velocity context:

            int month = (new GregorianCalendar()).get(Calendar.MONTH);
            // add 1 to month because it is 0 based
            context.put ("month", new Integer(month+1) );

Now all you need to do is add the comparison to the template:

Dear Mr/Mrs $customer.LastName
The total of your account statement is $customer.AccountTotal
Regards
Widgets and Gadgets Inc.
#if ($month == 12)
    And a merry Christmas to you and your family.
#end

The #if directive is straightforward: a Boolean test determines whether to include the directive block's text in the template's rendered output. You can also perform more complex comparisons than simple equality, but these are mostly template-engine specific, so I will not cover them.

The iteration directive is just as easy as the #if directive. Template engines allow you to iterate through any implementation of the Java Collections Framework, including Array, List, and Iterator. For JDK 1.2 and higher, Java's Vector and ArrayList both implement the List interface, which makes them suited for use in template-engine iteration directives.

Instead of inserting the account total, suppose you would rather iterate through the account transactions and print a detailed report. The getTransactions() method (see the example code) of a Customer object returns a List object that contains zero or more Transaction objects. Because List is included as part of the Java Collections Framework, you can iterate through its contents using the #foreach directive. Don't worry about casting the objects -- the reflection engine does that for you. You can see how this works in the next example template:

Dear Mr/Mrs $customer.LastName
#foreach ($transaction in $customer.Transactions)
    $transaction.Description    $transaction.Amount
#end
The total of your account statement is $customer.AccountTotal
Regards
Widgets and Gadgets Inc.

The general form of a #foreach directive is #foreach <item> in <list>. It iterates through the list, placing each element in the <item> parameter and rendering the blocked content. The #foreach block repeats for each element in the list. In effect, you say to the template engine: "Repeat this block of text for each element in the list while putting each list element in the <item> variable once."

Model-View-Controller

Before proceeding to the next example, you should think about what you have learned so far. The biggest advantage to using a template engine is that you separate the code or program logic from the presentation or output. Doing so allows you to change the program logic without worrying about the message; similarly, you (or a member of the public relations staff) can rewrite the message without recompiling the program.

In effect, you separate the data model (the data classes), the controller (the mailer program), and the view (template) components of the system. This three-layered approach is called the Model-View-Controller (MVC) model. If you follow the MVC model, your code separates into three distinct layers, easing the entire software development process for everybody involved. (MVC has been around for some time; see Resources for more information.) A model used with a template engine can be any Java object(s), preferably one that uses the Java Collection Framework. Your controller needs to be only context aware, which is easy to add. Some of the object-relational mapping tools for relational databases work extremely well with template engines and help to ease Java Database Connectivity operations; the same applies for Enterprise JavaBeans.

Template engines concentrate more on the view part of MVC. The template language syntax is powerful enough to process all the necessary view functions, but is at the same time simple enough for nonprogrammers to use. The template syntax not only shields template designers from unnecessary programming complexity; it protects the system from code that is deliberately or accidentally harmful. Template writers cannot, for example, write code that results in a nonterminating loop or allocates huge amounts of memory. Don't underestimate the value of this safety net; most template writers don't have a programming background, and shielding them from programming complexities saves you time in the long run.

Many template users believe that the clear separation between controller and view components as well as their inherent safeguarding make template engines a viable alternative to other publishing systems, like JavaServer Pages (JSPs). It therefore comes as no surprise that the most common use of template engines is as an alternative to JSPs.

HTML

Because people tend to place such an emphasis on template engines as an alternative to JSPs, they sometimes forget that templates are also more broadly applicable. HTML Web content is by far the most popular use of template engines. I have also generated SQL, email, XML, and even Java source code with a template engine. I can only cover a couple of template applications here, but I include some extra examples in Resources.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more