Sample Code


The sample code below is included in the package distribution.

Hello World

Here's a template for a "Hello, World!" servlet:

<html>
<head>
<title>Hello World Servlet</title>
</head>

<body bgcolor="#FFFFFF">

<p>${message}

</body>
</html>


Here's HelloServlet.java, which uses the above template:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import freemarker.template.*;

public class HelloServlet extends HttpServlet {

    private Template template;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        String templatePath = getServletContext().getRealPath("/hello-world.html");
        try {
            template = new Template(templatePath);
        } catch (IOException e) {
            throw new UnavailableException(this, "Can't load template " +
                templatePath + ": " + e.toString());
        }
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        res.setContentType("text/html");
        PrintWriter out = res.getWriter();

        // Make a template data model.
        SimpleHash modelRoot = new SimpleHash();
        modelRoot.put("message", new SimpleScalar("Hello, world!"));

        // Process the template.
        template.process(modelRoot, out);
        out.close();
    }

    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {
        doGet(req, res);
    }
}


You can run this servlet with your favorite servlet-running program; set the servlets directory and document root directory to the examples directory in the package distribution; then invoke it with something like http://localhost:8080/servlet/HelloServlet.  

Guestbook

Now for a somewhat more realistic example, a guestbook servlet. You can run it in the same way as the previous example, except that you must also use the servlet.properties file in the examples directory. This provides the alias guestbook for the servlet, as well as its initialization parameters. Then you can invoke the servlet with something like http://localhost:8080/servlet/guestbook.

Here's the template for the guestbook servlet:

<html>
<head>
    <title>Guestbook</title>
</head>

<body bgcolor="#E0E0FF">

<div align="center">
    <font face="verdana,arial,helvetica" size="6" color="#191970"><b>Guestbook</b></font>
</div>

<br><br><br>

<if entryAdded>
    Thank you for your entry.

    <p><a href="/servlet/guestbook">Add another entry</a>
<else>
    Enter your name and message:

    <p>
    <form action="/servlet/guestbook" method="post">
    <input type="hidden" name="dataSubmitted" value="true">

    <table border="0">
    <tr>
        <td align="right">Name:</td>
        <td><input type="text" name="guestName" size="20"></td>
    </tr>
    <tr>
        <td align="right" valign="top">Message:</td>
        <td><textarea name="message" wrap="virtual" cols="50" rows="5"></textarea></td>
    </tr>
    </table>
    <p><input type="submit" value="Submit">
</if>

<p>
<hr>
<p>

<if guestbook>

    <table border="1" cellspacing="0" cellpadding="5" width="100%">

    <tr>

        <td width="25%" align="center" bgcolor="#F0F0FF">
            <font face="verdana,arial,helvetica" size="+1" color="#191970">
                <b>Date</b>
            </font>
        </td>

        <td width="25%" align="center" bgcolor="#F0F0FF">
            <font face="verdana,arial,helvetica" size="+1" color="#191970">
                <b>Name</b>
            </font>
        </td>

        <td width="50%" align="center" bgcolor="#F0F0FF">
            <font face="verdana,arial,helvetica" size="+1" color="#191970">
                 <b>Message</b>
            </font>
        </td>

    </tr>

    <list guestbook as entry>

        <tr>
            <td bgcolor="#F0F0FF" valign="top">${entry.date}</td>
            <td bgcolor="#F0F0FF" valign="top">${entry.name}</td>
            <td bgcolor="#F0F0FF" valign="top">${entry.message}</td>
        </tr>
    </list>

    </table>

<else>

    There are no guestbook entries.

</if>

</body>
</html>


The easiest way to make a template data model would be to have the servlet store the guestbook entries in a SimpleList; each entry would be a SimpleHash of two SimpleScalars (name and message). In many cases, this approach is perfectly adequate. However, suppose we also want to be able to display our guestbook in some other form, e.g. in an applet. We can make the underlying guestbook classes as generic as possible, and write thin TemplateModel classes that provide an interface between them and the template. We can adapt Model/View/Controller architecture to this situation: the template is the view, and the servlet is the controller. The servlet is responsible for passing input to the generic model objects, connecting them to TemplateModel classes, putting the latter in a TemplateModelRoot, and processing the template.

We could take one of two approaches to designing our TemplateModel classes. In the first approach, we would give them the same interface as the generic model classes, and pass them the user's input; in this case, we would consider them to be wrapper (or "decorator") classes. In the second approach, we would give them only the interface that they need to have as TemplateModels, and pass them the generic model objects only after these have been initialized and given user input; we would then consider our TemplateModel classes to be adapter classes. In this example, we will take the adapter approach.

An object that has named properties needs an adapter that implements TemplateHashModel; an object that represents a list of elements needs an adapter that implements TemplateListModel.

First, we'll need a generic model class Guestbook, which maintains a list of GuestbookEntry objects:

package guestbook;

import java.util.*;

public class Guestbook {

    private LinkedList list = new LinkedList();

    public Guestbook() { }

    public void addEntry(String name, String message) {
        list.add(new GuestbookEntry(name, message));
    }

    public List getList() {
        return list;
    }
}

package guestbook;

import java.util.Date;

public class GuestbookEntry {

    private Date date;
    private String name;
    private String message;

    public GuestbookEntry(String name, String message) {
        date = new Date();
        this.name = name;
        this.message = message;
    }

    public Date getDate() {
        return date;
    }

    public String getName() {
        return name;
    }

    public String getMessage() {
        return message;
    }
}


We can now make the TemplateModel adapters for these classes:

package guestbook;

import freemarker.template.*;
import java.util.*;

public class GuestbookTM implements TemplateListModel {

    private List entries;
    private ListIterator iterator;

    public GuestbookTM(Guestbook book) {
        this.entries = book.getList();
    }

    public boolean isEmpty() throws TemplateModelException {
        return entries.isEmpty();
    }

    public boolean hasNext() throws TemplateModelException {
        checkIterator();
        return iterator.hasNext();
    }

    public TemplateModel next() throws TemplateModelException {
        checkIterator();
        if (iterator.hasNext()) {
            return new GuestbookEntryTM((GuestbookEntry)iterator.next());
        } else {
            throw new TemplateModelException("No more elements.");
        }
    }

    private void checkIterator() {
        if (iterator == null) {
            iterator = entries.listIterator();
        }
    }

    public void rewind() throws TemplateModelException {
        iterator = null;
    }

    public boolean isRewound() throws TemplateModelException {
        return (iterator == null);
    }
}


Our adapter for GuestbookEntry can read its GuestbookEntry's values as needed, store them in objects implementing TemplateScalarModel, and store these as instance variables of its own. Since the values are just String and Date objects, they can conveniently be stored in SimpleScalar objects, once the Date object is properly formatted for output.

package guestbook;

import freemarker.template.*;
import java.util.Date;
import java.util.TimeZone;
import java.text.DateFormat;

public class GuestbookEntryTM implements TemplateHashModel {

    private GuestbookEntry entry;

    private SimpleScalar date;
    private SimpleScalar name;
    private SimpleScalar message;

    public GuestbookEntryTM(GuestbookEntry entry) {
        this.entry = entry;
    }

    public TemplateModel get(String key) throws TemplateModelException {
        if (key.equals("date")) {
            return getDate();
        } else if (key.equals("name")) {
            return getName();
        } else if (key.equals("message")) {
            return getMessage();
        } else {
            return null;
        }
    }

    private TemplateModel getDate() {
        if (date == null) {
            DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
                DateFormat.LONG);
            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
            date = new SimpleScalar(dateFormat.format(entry.getDate()));
        }
        return date;
    }

    private TemplateModel getName() {
        if (name == null) {
            name = new SimpleScalar(entry.getName());
        }
        return name;
    }

    private TemplateModel getMessage() {
        if (message == null) {
            message = new SimpleScalar(entry.getMessage());
        }
        return message;
    }

    public boolean isEmpty() throws TemplateModelException {
        return (entry == null);
    }
}
 

Our servlet will store its single template, guestbook_template.html, in a TemplateCache so it will be automatically reloaded when changed. TemplateCache objects are explained here. The servlet will rely on its environment to provide it with the path of its template directory, and the suffix _template.html of its template file.

package guestbook;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import freemarker.template.*;

public class GuestbookServlet extends HttpServlet implements CacheListener {

    private Guestbook book;
    private FileTemplateCache templateCache;
    private String templateSuffix;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        String templatePath = getInitParameter("templatePath");
        if (templatePath == null) {
            throw new UnavailableException(this, "Init parameter templatePath not set.");
        }

        templateSuffix = getInitParameter("templateSuffix");
        if (templateSuffix == null) {
            throw new UnavailableException(this, "Init parameter templateSuffix not set.");
        }

        String realPath = getServletContext().getRealPath(templatePath);
        templateCache = new FileTemplateCache(realPath);
        templateCache.setFilenameSuffix(templateSuffix);
        templateCache.addCacheListener(this);
        templateCache.startAutoUpdate();
        book = new Guestbook();
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        res.setContentType("text/html");
        PrintWriter out = res.getWriter();

        // Get the guestbook's template.
        Template template = getTemplate("guestbook" + templateSuffix);

        // Make the root node of the data model.
        SimpleHash modelRoot = new SimpleHash();

        // If an entry was submitted, add it to the guestbook.
        if (req.getParameter("dataSubmitted") != null) {
            book.addEntry(req.getParameter("guestName"), req.getParameter("message"));
            modelRoot.put("entryAdded", new SimpleScalar(true));
        }

        // Wrap the guestbook in a template model adapter.
        GuestbookTM bookTM = new GuestbookTM(book);
        modelRoot.put("guestbook", bookTM);

        // Process the template.
        template.process(modelRoot, out);

        out.close();
    }

    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {
        doGet(req, res);
    }

    private Template getTemplate(String templateName) throws ServletException {
        Template template = templateCache.getTemplate(templateName);
        if (template == null) {
            throw new ServletException("Template " +
                templateName +
                " is not available.");
        } else {
            return template;
        }
    }

    public void cacheUnavailable(CacheEvent e) {
        System.out.println("Cache unavailable: " + e.getException().toString());
    }

    public void elementUpdated(CacheEvent e) {
        System.out.println("Template updated: " + e.getElementName());
    }

    public void elementUpdateFailed(CacheEvent e) {
        System.out.println("Update of template " + e.getElementName() +
            " failed: " + e.getException().toString());
    }

    public void elementRemoved(CacheEvent e) {
        System.out.println("Template removed: " + e.getElementName());        
    }

    public String getServletInfo() {
        return "Guestbook Servlet";
    }
}

Previous: Data Model Interfaces Next: Template Caches