Easy Display Logic with Templates

Templates allow you to unclutter your servlets by separating the processing logic from the display logic. This guide discusses how to effectively use this tool.

There are eight sections, intended to walk you through a simple, and then a more complex example.

  1. Writing the Template
  2. Using the Template with Your Servlets
  3. What Templates Are Not
  4. Avoiding the Construction of Large Strings
  5. How to Load a Template
  6. Using Templates to Implement a Consistent Look
  7. Additional Notes
  8. Limitations

1. Writing the Template

Let's start with a simple template:

<html>
    <head>
        <title>Greetings Page</title>
    </head>
    <body>
        <p>Hello, #name#.</p>
        <p>This is page access ##: #accessCount#</p>
    </body>
<html>

The text highlighted in red, and surrounded by octothorpes (the '#' characters) should draw your attention. These are the template variables. They will be replaced by values defined later during the course of template processing.

It can be seen, then, that templates consist of text and variables.

Template Rules

There are only three simple rules to follow:

  1. Variables are surrounded by '#' characters,
  2. Two '#' characters in a row stand for a single '#' — think of this as an "empty" variable — and
  3. A variable name must be a valid Java identifier. If it is not, then it is ignored and used as-is.

The template processor recognizes a variable by a '#', a type of "escape". This is the purpose of using a double '#' to represent a single '#'. It differentiates a variable from an actual '#' character.

For the example above, there are two variables named name and accessCount, and one occurence of a single '#'.

[Top]


2. Using the Template with Your Servlets

Those of you who have written servlets that output more complex HTML will know that it becomes very tedious and cluttered to include all the display logic in addition to the processing logic. A template allows you to remove a good portion of the HTML-generating code.

For the template example in the previous section, let's write a servlet that makes use of the name and accessCount variables.

Initialize the Template

First, the template must be processed by the template engine. This only needs to occur once. A good place to do this is in the servlet's init() method:

private static final String MY_TEMPLATE =
        "<html>"
        + "<head><title>Greetings Page</title></head>"
        + "<body>"
        + "<p>Hello, #name#.</p>"
        + "<p>This is page access ##: #accessCount#</p>"
        + "</body></html>";

private Template template;

public void init() {
    template = Template.parse(MY_TEMPLATE);
}

Utilize the Template

Next, set each template variable when a request is made of the servlet. For this example, let's set the name variable to the value of a parameter called user. Also keep track of the access count in a field.

private int accessCount;

public void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException
{
    // Update the access count

    accessCount++;

    // Output the template,
    // but synchronize on it to prevent concurrent modification
    // by multiple requests

    resp.setContentType("text/html");

    synchronized (template) {
        template.set("name", req.getParameter("user"));
        template.set("accessCount", Integer.toString(accessCount));
        template.output(req, resp);
    }
}

Servlets are not, by default, thread-safe. This means that if you use any resources that need to be accessed atomically, then this fact must be considered. Templates are such a resource. We don't want the variables to change during the template output, so we need to lock its use.

[Top]


3. What Templates Are Not

The template engine does not currently do anything other than variable substitution. While this is not as powerful as a full-fledged server-side processing engine such as JSP, it is still a very useful tool. We shall see this in the additional example below.

[Top]


4. Avoiding the Construction of Large Strings

It may be the case that a variable needs to be replaced with a large or complex string. It would waste memory if the string were constructed each time. A callback mechanism is in place to solve this problem.

If a variable is unset when the template is output, then the engine will call a special method in an interface named Template.UnsetVariableHandler. Classes that implement this interface can "listen" for any unset variables and then output something appropriate in the templateVariable method. A good class with which to implement this interface is the servlet that is using the template.

The following servlet demonstrates how to use this feature.

import com.qindesign.servlet.Template;
...Other imports...

public class MyServlet extends HttpServlet
    implements Template.UnsetVariableHandler
{
    private static final String MY_TEMPLATE =
            "<html>"
            + "<head><title>Template Page</title></head>"
            + "<body>"
            + "<h1>A Title</h1>"
            + "#pageContent#"
            + "</body></html>";

    private Template template;

    /**
     * Initialize the servlet.
     */
    public void init() {
        template = Template.parse(MY_TEMPLATE);
        template.setUnsetVariableHandler(this);
    }

    /**
     * Output the content for any unset variables.
     *
     * @param varName the variable name
     * @param out the servlet output stream from the response object
     * @param req the HTTP request object
     * @param resp the HTTP response object
     * @throws IOException if there was an I/O error while outputting the
     *         template.
     * @throws ServletException if the request could not be handled.
     */
    public void templateVariable(String varName,
                                 ServletOutputStream out,
                                 HttpServletRequest req
                                 HttpServletResponse resp) {
        // Only handle the variables we're interested in,
        // in case there's others

        if (!"pageContent".equals(varName)) {
            return;
        }

        out.print("<p>Some long content.</p>");
    }

    /**
     * Handle a GET request.
     */
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        resp.setContentType("text/html");

        // Output the template
        // Note that the "pageContent" variable is not set

        synchronized (template) {
            template.output(req, resp);
        }
    }

It should be emphasized that you do not need to implement template handling in this way if the variables are replaced with relatively simple strings. The techniques in Section 2 should suffice.

Additionally, the two methods can be combined. It is possible to set variables using the set(String, String) method from Section 2 and the unset variable handling from this section.

[Top]


5. How to Load a Template

We have already seen how to load a template from a string. The static Template.parse(String) method has been provided for this purpose.

It is also possible to load a template from a file using the static Template.parse(File) method. The advantage of this is that your servlets do not need to be rebuilt each time the templates change.

Because several servlets may use the same template file, the template engine implements a cache that stores the data for previously parsed files. This improves startup time and avoids redundant memory usage. To clear this cache, call the static Template.flushCache() method.

[Top]


6. Using Templates to Implement a Consistent Look

Templates can be used to implement a consistent look across a number of servlets. An easy way to do this is to create a template that is loaded from a file, and that contains variables for each section that must be unique.

For example, the HTML template below has a consistent header and footer, and a replaceable body, header, and page title.

<html>
    <head>
        <title>#pageTitle#</title>
    </head>
    <body>
        <p>Some Company, Inc.</p>
        <hr>

        <h1>#heading#</h1>

        #pageContent#

        <hr>
        <p align="center"><em>&copy;2004 Some Company, Inc.</em></p>
    </body>
<html>

Say this HTML is saved in a file named the_template.html in the root HTML location (in other words, a browser can request /the_template.html). A servlet that uses this template may look something like the code below.

import com.qindesign.servlet.Template;
...Other imports...

public class AServlet extends HttpServlet
    implements Template.UnsetVariableHandler
{
    private Template template;

    /**
     * Initialize the servlet.
     */
    public void init() {
        // Load the_template.html from the same place as the other HTML files

        template = Template.parse(
            new File(getServletContext().getRealPath("/the_template.html")));
        template.setUnsetVariableHandler(this);
    }

    /**
     * Output the content for any unset variables.
     *
     * @param varName the variable name
     * @param out the servlet output stream from the response object
     * @param req the HTTP request object
     * @param resp the HTTP response object
     * @throws IOException if there was an I/O error while outputting the
     *         template.
     * @throws ServletException if the request could not be handled.
     */
    public void templateVariable(String varName,
                                 ServletOutputStream out,
                                 HttpServletRequest req
                                 HttpServletResponse resp) {
        // Only handle the variables we're interested in,
        // in case there's others that are unset
        // The "pageTitle" and "heading" variables are already set

        if (!"pageContent".equals(varName)) {
            return;
        }

        out.print("<p>Content that is specific to this servlet.</p>");
    }

    /**
     * Handle a GET request.
     */
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        resp.setContentType("text/html");

        // Output the template
        // Note that the "pageContent" variable is not set, but the others are

        synchronized (template) {
            template.set("pageTitle", "This Servlet's Page Title");
            template.set("heading", "Heading for This Servlet");
            template.output(req, resp);
        }
    }

Using stylesheets is beyond the scope of this guide, but it is the recommended way to maintain a consistent look across different pages. They can be included in your templates in exactly the same way as they would be included in ordinary HTML pages.

[Top]


7. Additional Notes

It is possible to use more than one template at a time. For example, a standard header and footer could be provided by one template, and the content produced by another.

Furthermore, templates are not limited to HTML. Any sort of text can be expressed in a template.

[Top]


8. Limitations

There are a few minor limitations about the template engine that should be described.

First, templates cannot be accessed concurrently. The sections above show how to use synchronized blocks to protect the template object when using it when handling a request.

Second, templates that are loaded from a file do not check if the file itself is updated. This means that the server must be restarted for any template changes to take effect.

Last, templates will not recognize nested variables. For example, say you replace the variable "myVariable" with "Hello, #name#" using a set call. The "#name#" portion of the string will not be recognized as an additional variable. This is not really a limitation of the engine since it is designed to be lightweight, however there are those that may wonder about such a feature. But... it is possible to have the template output to another string, which in turn would be parsed into another template object.

[Top]