JSP templates

Use JSP templates to encapsulate Webpage layout and encourage modular design

Although Web development tools are rapidly progressing, they still lag behind most graphical user interface (GUI) toolkits such as Swing or VisualWorks Smalltalk. For example, traditional GUI toolkits provide layout managers, in one form or another, that allow layout algorithms to be encapsulated and reused. This article explores a template mechanism for JavaServer Pages (JSP) that, like layout managers, encapsulates layout so it can be reused instead of replicated.

Because layout undergoes many changes over the course of development, it's important to encapsulate that functionality so it can be modified with minimal impact to the rest of the application. In fact, layout managers demonstrate an example of one of the tenets of object-oriented design: encapsulate the concept that varies, which is also a fundamental theme for many design patterns.

JSP does not provide direct support for encapsulating layout, so Webpages with identical formats usually replicate layout code; for example, Figure 1 shows a Webpage containing header, footer, sidebar, and main content sections.

Figure 1. Webpage layout

The layout of the page shown in Figure 1 is implemented with HTML table tags:

Example 1. Including content

<html><head><title>JSP Templates</title></head>
<body background='graphics/background.jpg'>
<table>
   <tr valign='top'><td><%@include file='sidebar.html'%></td>
      <td><table>
         <tr><td><%@include file='header.html'%></td></tr>
         <tr><td><%@include file='introduction.html'%></td></tr>
         <tr><td><%@include file='footer.html'%></td></tr>
         </table>
      </td>
   </tr>
</table>
</body></html>

In the example listed above, content is included with the JSP include directive, which allows the content of the page to vary -- by changing the included files -- without modifying the page itself. However, because layout is hard coded, layout changes require modifications to the page. If a Website has multiple pages with identical formats, which is common, even simple layout changes require modifications to all of the pages.

To minimize the impact of layout changes, we need a mechanism for including layout in addition to content; that way, both layout and content can vary without modifying files that use them. That mechanism is JSP templates.

Using templates

Templates are JSP files that include parameterized content. The templates discussed in this article are implemented with a set of custom tags: template:get, template:put, and template:insert. The template:get tag accesses parameterized content, as illustrated in Example 2.a, which produces Webpages with the format shown in Figure 1.

Example 2.a. A template

<%@ taglib uri='/WEB-INF/tlds/template.tld' prefix='template' %>
<html><head><title><template:get name='title'/></title></head>
<body background='graphics/background.jpg'>
<table>
   <tr valign='top'><td><template:get name='sidebar'/></td>
      <td><table>
            <tr><td><template:get name='header'/></td></tr>
            <tr><td><template:get name='content'/></td></tr>
            <tr><td><template:get name='footer'/></td></tr>
         </table>
      </td>
   </tr>
</table>
</body></html>

Example 2.a is nearly identical to Example 1, except we use template:get instead of the include directive. Let's examine how template:get works.

template:get retrieves a Java bean with the specified name from request scope. The bean contains the URI (Uniform Resource Identifier) of a Web component that's included by template:get. For example, in the template listed in Example 2.a, template:get obtains a URI -- header.html -- from a bean named header in request scope. Subsequently, template:get includes header.html.

template:put puts the beans in request scope that are subsequently retrieved by template:get. The template is included with template:insert. Example 2.b illustrates the use of the put and insert tags:

Example 2.b. Using the template from Example 2.a

<%@ taglib uri='/WEB-INF/tlds/template.tld' prefix='template' %>
<template:insert template='/articleTemplate.jsp'>
  <template:put name='title' content='Templates' direct='true'/>
  <template:put name='header' content='/header.html' />
  <template:put name='sidebar' content='/sidebar.jsp' />
  <template:put name='content' content='/introduction.html'/>
  <template:put name='footer' content='/footer.html' />
</template:insert>

The insert start tag specifies the template to be included, in this case the template listed in Example 2.a. Each put tag stores a bean in request scope and the insert end tag includes the template. The template subsequently accesses the beans as described above.

A direct attribute can be specified for template:put; if direct is set to true, the content associated with the tag is not included by template:get, but is printed directly to the implicit out variable. In Example 2.b, for example, the title content -- JSP Templates -- is used for the window title.

Websites containing multiple pages with identical formats have one template, such as the one listed in Example 2.a, and many JSP pages, such as Example 2.b, that use the template. If the format is modified, changes are restricted to the template.

Another benefit of templates and including content in general is modular design. For example, the JSP file listed in Example 2.b ultimately includes header.html, listed in Example 2.c.

Example 2.c. header.html

<table>
   <tr>
      <td><img src='graphics/java.jpg'/></td>
      <td><img src='graphics/templates.jpg'/></td>
   </tr>
</table><hr>

Because header.html is included content, it does not have to be replicated among pages that display a header. Also, although header.html is an HTML file, it does not contain the usual preamble of HTML tags such as <html> or <body> because those tags are defined by the template. That is, because the template includes header.html, those tags should not be repeated in header.html.

Note: JSP provides two ways to include content: statically, with the include directive, and dynamically, with the include action. The include directive includes the source of the target page at compile time and is equivalent to C's #include or Java's import. The include action includes the target's response generated at runtime.

Like the JSP include action, templates include content dynamically. So, although the JSP pages in Example 1 and Example 2.b are functionally identical, the former statically includes content, whereas the latter dynamically includes it.

Optional content

All template content is optional, which makes a single template useful to more Webpages. For example, Figure 2.a and Figure 2.b show two pages -- login and inventory -- that use the same template. Both pages have a header, footer, and main content. The inventory page has an edit panel (which the login page lacks) for making inventory changes.

Figure 2.a. A login form
Figure 2.b. An inventory page

Below, you'll find the template shared by the login and inventory pages:

<%@ taglib uri='template.tld' prefix='template' %>
...
<table width='670'>
   <tr><td width='60'></td>
      <td><template:get name='header'/></td></tr>
   <tr><td width='60'></td>
      <td><template:get name='main-content'/></td></tr>
   <tr><td width='60'></td>
       <td><template:get name='editPanel'/></td></tr>
   <tr><td width='60'></td>
   <td><template:get name='footer'/></td></tr>
</table>
...

The inventory page uses the template listed above and specifies content for the edit panel:

<%@ taglib uri='template.tld' prefix='template' %>
<%@ taglib uri='security.tld' prefix='security' %>
<template:insert template='/template.jsp'>
   ...
   <template:put name='editPanel'
                    content='/editPanelContent.jsp'/>
   ...
</template:insert>

In contrast, the login page does not specify content for the edit panel:

<%@ taglib uri='template.tld' prefix='template' %>
<template:insert template='/template.jsp'>
   <template:put name='title' content='Login' direct='true'/>
   <template:put name='header' content='/header.jsp'/>
   <template:put name='main-content'
                 content='/login.jsp'/>
   <template:put name='footer' content='/footer.jsp'/>
</template:insert>

Because the login page does not specify content for the edit panel, it's not included.

Role-based content

Web applications often discriminate content based on a user's role. For example, the same JSP template, which includes the edit panel only when the user's role is curator, produces the two pages shown in Figures 3.a and 3.b.

Figure 3.a. Inventory page for curators
Figure 3.b. Inventory page for other users

The template used in Figures 3.a and 3.b uses template:get's role attribute:

<%@ taglib uri='template.tld' prefix='template' %>
...
<table>
   ...
   <td><template:get name='editPanel' role='curator'/></td></tr>
   ...
</table>
...

The get tag includes content only if the user's role matches the role attribute. Let's look at how the tag handler for template:get uses the role attribute:

public class GetTag extends TagSupport {
   private String name = null, role = null;
   ...
   public void setRole(String role) { this.role = role; }
   ...
   public int doStartTag() throws JspException {
      ...
      if(param != null) {
         if(roleIsValid()) {
            // include or print content ...
         }
      }
   ...
   }
   private boolean roleIsValid() {
      return role == null || // valid if role isn't set
         ((javax.servlet.http.HttpServletRequest)
          pageContext.getRequest()).isUserInRole(role);
   }
}

Implementing templates

The templates discussed in this article are implemented with three custom tags:

  • template:insert
  • template:put
  • template:get

The insert tag includes a template, but before it does, put tags store information -- a name, URI, and Boolean value specifying whether content should be included or printed directly -- about the content the template includes. template:get, which includes (or prints) the specified content, subsequently accesses the information.

template:put stores beans in request scope but not directly because if two templates use the same content names, a nested template could overwrite the enclosing template's content.

To ensure that each template has access only to its own information, template:insert maintains a stack of hashtables. Each insert start tag creates a hashtable and pushes it on the stack. The enclosed put tags create beans and store them in the newly created hashtable. Subsequently, get tags in the included template access the beans in the hashtable. Figure 4 shows how the stack is maintained for nested templates.

Figure 4. Storing template parameters in request scope

Each template in Figure 4 accesses the correct footer; footer.html for template_1.jsp and footer_2.html for template_2.jsp. If the beans were stored directly in request scope, step 5 in Figure 4 would overwrite the footer bean specified in step 2.

Template tag implementations

The remainder of this article examines the implementation of the three template tags: insert, put, and get. We begin with sequence diagrams, starting with Figure 5. It illustrates the sequence of events for the insert and put tags when a template is used.

Figure 5. Sequence diagrams for the put and insert tags

If a template stack does not already exist, the insert start tag creates one and places it in request scope. A hashtable is subsequently created and pushed on the stack.

Each put start tag creates a PageParameter bean, stored in the hashtable created by the enclosing insert tag.

The insert end tag includes the template. The template uses get tags to access the beans created by put tags. After the template is processed, the hashtable created by the insert start tag is popped off the stack.

Figure 6 shows the sequence diagram for template:get.

Figure 6. Sequence diagrams for the get tag

Template tag listings

Tag handler implementations for the template tags prove straightforward. Example 3.a lists the InsertTag class -- the tag handler for template:insert.

Example 3.a. InsertTag.java

package tags.templates;
import java.util.Hashtable;
import java.util.Stack;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
public class InsertTag extends TagSupport {
   private String template;
   private Stack stack;
   // setter method for template attribute
   public void setTemplate(String template) {
      this.template = template;
   }
   public int doStartTag() throws JspException {
      stack = getStack(); // obtain a reference to the template stack
      stack.push(new Hashtable()); // push new hashtable onto stack
      return EVAL_BODY_INCLUDE;  // pass tag body through unchanged
   }
   public int doEndTag() throws JspException {
      try {
         pageContext.include(template); // include template
      }
      catch(Exception ex) { // IOException or ServletException
         throw new JspException(ex.getMessage()); // recast exception
      }
      stack.pop(); // pop hashtable off stack
      return EVAL_PAGE; // evaluate the rest of the page after the tag
   }
   // tag handlers should always implement release() because
   // handlers can be reused by the JSP container
   public void release() {
      template = null;
      stack = null;
   }
   public Stack getStack() {
      // try to get stack from request scope
      Stack s = (Stack)pageContext.getAttribute(
                        "template-stack",
                        PageContext.REQUEST_SCOPE);
      // if the stack's not present, create a new one and
      // put it into request scope
      if(s == null) {
         s = new Stack();
         pageContext.setAttribute("template-stack", s,
                        PageContext.REQUEST_SCOPE);
      }
      return s;
   }
}

Example 3.b lists the PutTag class, the tag handler for template:put:

Example 3.b. PutTag.java

package tags.templates;
import java.util.Hashtable;
import java.util.Stack;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import beans.templates.PageParameter;
public class PutTag extends TagSupport {
   private String name, content, direct="false";
   // setter methods for Put tag attributes
   public void setName(String s) { name = s; }
   public void setContent(String s) {content = s; }
   public void setDirect(String s) { direct = s; }
   public int doStartTag() throws JspException {
      // obtain a reference to enclosing insert tag
      InsertTag parent = (InsertTag)getAncestor(
                              "tags.templates.InsertTag");
      // put tags must be enclosed in an insert tag
      if(parent == null)
         throw new JspException("PutTag.doStartTag(): " +
                                "No InsertTag ancestor");
      // get template stack from insert tag
      Stack template_stack = parent.getStack();
      // template stack should never be null
      if(template_stack == null)
         throw new JspException("PutTag: no template stack");
      // peek at hashtable on the stack
      Hashtable params = (Hashtable)template_stack.peek();
      // hashtable should never be null either
      if(params == null)
         throw new JspException("PutTag: no hashtable");
      // put a new PageParameter in the hashtable
      params.put(name, new PageParameter(content, direct));
      return SKIP_BODY; // not interested in tag body, if present
   }
   // tag handlers should always implement release() because
   // handlers can be reused by the JSP container
   public void release() {
      name = content = direct = null;
   }
   // convenience method for finding ancestor names with
   // a specific class name
   private TagSupport getAncestor(String className)
                                 throws JspException {
      Class klass = null; // can't name variable "class"
      try {
         klass = Class.forName(className);
      }
      catch(ClassNotFoundException ex) {
         throw new JspException(ex.getMessage());
      }
      return (TagSupport)findAncestorWithClass(this, klass);
   }
}

PutTag.doStartTag creates a PageParameter bean -- listed in Example 3.c. -- subsequently stored in request scope.

Example 3.c. PageParameter.java

package beans.templates;
public class PageParameter {
   private String content, direct;
   public void setContent(String s) {content = s; }
   public void setDirect(String s) { direct = s; }
   public String getContent() { return content;}
   public boolean isDirect() { return Boolean.valueOf(direct).booleanValue(); }
   public PageParameter(String content, String direct) {
      this.content = content;
      this.direct = direct;
   }
}

PageParameter serves as a simple placeholder for the content and direct attributes set in the template:put tag. We see the GetTag class, the tag handler for template:get, in Example 3.d:

Example 3.d. GetTag.java

package tags.templates;
import java.util.Hashtable;
import java.util.Stack;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import beans.templates.PageParameter;
public class GetTag extends TagSupport {
   private String name;
   // setter method for name attribute
   public void setName(String name) {
      this.name = name;
   }
   public int doStartTag() throws JspException {
      // obtain reference to template stack
      Stack stack = (Stack)pageContext.getAttribute(
               "template-stack", PageContext.REQUEST_SCOPE);
      // stack should not be null
      if(stack == null)
         throw new JspException("GetTag.doStartTag(): " +
                                 "NO STACK");
      // peek at hashtable
      Hashtable params = (Hashtable)stack.peek();
      // hashtable should not be null
      if(params == null)
         throw new JspException("GetTag.doStartTag(): " +
                                 "NO HASHTABLE");
      // get page parameter from hashtable
      PageParameter param = (PageParameter)params.get(name);
      if(param != null) {
         String content = param.getContent();
         if(param.isDirect()) {
            // print content if direct attribute is true
            try {
               pageContext.getOut().print(content);
            }
            catch(java.io.IOException ex) {
               throw new JspException(ex.getMessage());
            }
         }
         else {
            // include content if direct attribute is false
            try {
               pageContext.getOut().flush();
               pageContext.include(content);
            }
            catch(Exception ex) {
               throw new JspException(ex.getMessage());
            }
         }
      }
      return SKIP_BODY; // not interested in tag body, if present
   }
   // tag handlers should always implement release() because
   // handlers can be reused by the JSP container
   public void release() {
      name = null;
   }
}

GetTag.doStartTag retrieves the page parameter bean from request scope and obtains the content and direct properties from the bean. Subsequently, the content is either included or printed, depending on the value of the direct property.

Conclusion

Templates are a simple but useful concept that should be in every JSP developer's repertoire. Templates encapsulate layout and therefore minimize the impact of layout changes. Further, templates can discriminate content based on user roles and can be nested within other templates and JSP pages.

Templates are not a standard JSP feature, but you can easily implement them with custom tags, as discussed in this article. Those tags can be used as is, or they can be used as the baseline for a template mechanism with more features.

Learn more about this topic

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