![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
SummaryBy Leon Messerschmidt
Using text-based templates for tasks like HTML generation and mail merging can liberate developers from mundane and error-prone text generation code. In this article, Leon Messerschmidt shows the advantages of templates and how to create effective templates for different scenarios. You'll never useSystem.println()
again. (2,300 words)
ost 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
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.
For the HTML example, I will use the same model as I did in the email examples. The HTML screen is a hypothetical view in an organization's intranet that shows the details of customer accounts. The controller class in this case is a Java servlet, and the view consists of an HTML template. The code below illustrates the most interesting part of the servlet class. I wrote my servlet from scratch to make it more general, but template engines generally provide some servlet tools for you to ease the workload a bit.
//
Load the
template
Template
template =
Velocity.getTemplate("html.vm");
//
Create the
context
VelocityContext
context = new
VelocityContext();
context.put
("customers",Customer.getCustomers());
//
Render the servlet and return the
response
ServletOutputStream
output =
response.getOutputStream();
Writer
writer = new OutputStreamWriter
(output);
template.merge(context,
writer);
writer.flush();
You'll find nothing surprising here. I still just add the necessary object to
the context and render the template. However, note that in the previous example
I only added one Customer
to the context, whereas I now add a list
of Customer
objects to the context. I can iterate through all
Customer
s with a #foreach
directive. Here is the
accompanying HTML template:
<html>
<body>
<h1>Customer
Report</h1>
#foreach
($customer in
$customers)
<h2>$customer.FirstName
$customer.LastName<h2>
<table>
#foreach
($transaction in
$customer.Transactions)
<tr>
<td
width="200">
$transaction.Date
</td>
<td
width="150">
$transaction.Description
</td>
<td
width="100">
$transaction.Amount
</td>
</tr>
#end
</tr>
<td></td>
<td></td>
<td><b>$customer.AccountTotal</b></td>
<tr>
</table>
#end
</body>
</html>
If you're planning a project that requires more than just a handful of HTML templates, consider one of the many template-based frameworks available. These frameworks give you the elegance of a template engine for generating HTML, while supplying you with a valuable set of tools, like database connection pooling and security, on which to build your application. Two common examples are Turbine and Melati, which are both compatible with Velocity and WebMacro. Both are open source and free.
Performance and configuration
In
most programs, templates seem fast enough; but for high-volume Websites, you
must look closely at performance. A template engine's most important performance
feature is the caching of templates. Instead of loading the template from disk
for each request, the template is parsed and stored in an optimal in-memory
format. Template caching is usually disabled by default during development:
traffic volume is low, and you want changes in templates to reflect immediately.
After deployment, the templates will usually not change, and performance becomes
a higher priority. Thus, at that point you should enable template caching.
In most template engines you can easily enable template caching by applying a
setting or editing a Java properties file. In Velocity you can initiate the
template with a Properties
object. You can either create a
Properties
object manually, as I have done below, or load it from a
properties file, which is probably better for an actual system.
Properties
props = new
Properties();
props.setProperty(
"file.resource.loader.cache", "true"
);
props.setProperty(
"file.resource.loader.modificationCheckInterval", "3600"
);
Velocity.init
(props);
The file.resource.loader.cache
property sets caching to
true
or false
, while the
file.resource.loader.modificationCheckInterval
property sets the
time in seconds for checking file changes. I cannot cover all the properties
here in detail; consult your template engine's documentation for more
information.
Free yourself from tedious
programming
Advanced template engines are freely available
and help you add template capabilities to almost any Java application. These
engines provide easy-to-use tools for programmers and a simple template syntax
for template writers so that developers can code programs with confidence.
You have learned that, by separating source code from presentation, templates can greatly ease the jobs of programmers and content creators alike. Templates liberate programmers from littering source code with messages and empower others to freely write content without interfering with program logic.
The clear separation that templates offer between logic and presentation also
provides you with a better MVC design. This makes an attractive alternative to
other publishing systems like JSPs, for it improves your applications' overall
design without adding extra complexity.
About the author
Leon
Messerschmidt, a Java developer and open source advocate, is currently
working at Opticode Software. He has been
involved in both commercial and open source Web application framework
development for many years.
![]() |
![]() |
|
![]() |
Copyright © 2004 JavaWorld.com, an IDG company |
![]() |
![]() |
![]() |