Custom Tags

JSP and Java beans provide a nice way to abstract data access away from the page itself, but it still doesn’t get code entirely out of the system. Page designers still must use script elements for control functions such as loops or branching logic. In addition, Java beans sometimes aren’t enough to fully encapsulate a web application’s internal logic, leaving large chunks of code embedded in JSPs. And of course, there’s the matter of code reuse. While utility methods can be abstracted out into classes and made available to JSPs, scripting code embedded within a particular JSP tends not to lend itself to abstraction.

The JSP specification addresses this by allowing the creation of custom tags. Tags are organized into tag libraries , which can be loaded by pages as needed. Each tag library gets its own namespace and can be shared by as many pages as necessary. Properly designed custom tags can cut most, if not all, of the remaining pure Java code out your JSPs. We’ve already seen tags in action, since JSTL is just a collection of JSP tags that are a standard part of the environment.

Imagine a simple tag that displays the current date and time. Let’s call the tag Date. After creating and configuring it, we can access it within a JSP page:

<enterprise:Date/>

Note that the namespace is no longer jsp. Instead, we have a custom specified namespace that identifies a tag library, or taglib, containing a set of custom tags. We declare the tag library at the beginning of the JSP file with the <%@ taglib %> directive:

<%@ taglib
     uri="http://oreilly.com/enterprise/examples-taglib"
     prefix="enterprise"%>

The taglib containing the Date tag is identified by a specific URI. First, the server looks in the web.xml file for a mapping from the URI to a taglib file in the web archive. If there is not a mapping, then the server tries to find the taglib using the URI as a direct path. A taglib mapping is made in the web.xml file using a <taglib> element, like so:

<taglib>
<taglib-uri>http://oreilly.com/enterprise/examples-taglib</taglib-uri>
<taglib-location>
     /WEB-INF/jsp/enterprise-taglib.tld
    </taglib-location>
</taglib>

The <taglib-location> tag points to a .tld file, which is an XML-formatted tag library deployment descriptor. Here’s the .tld file that describes a single tag library:

<?xml version="1.0" encoding="ISO-8859-1"?>
<taglib
      xmlns="http://java.sun.com/xml/ns/j2ee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation=
        "http://java.sun.com/xml/ns/j2ee
         http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
      version="2.0">
    <description>A simple tag library for JEIAN</description>
    <tlib-version>1.0</tlib-version>
    <short-name>enterprise</short-name>
    <uri>http://oreilly.com/enterprise/examples-taglib</uri>
    <tag>
        <description>Display current date</description>
        <name>Date</name>
        <tag-class>com.oreilly.jent.jsp.DateTag</tag-class>
        <body-content>empty</body-content>
    </tag>
</taglib>

The key thing here is the <tag> tag with its subtags, <name> and <tag-class> (descriptions are optional). The <name> tag specifies the name that JSPs will use to refer to the tag itself, and the <tag-class> specifies the Java class that implements the tag functionality. In this case, it’s a class named DateTag in the enterprise package. This class file must be available to the web application.

By this point, we’ve described a tag, put it in a library, associated the library with the web application, retrieved the library from the web application for use in a JSP page, mapped it to the enterprise namespace for the page, and invoked the tag itself. The only thing remaining is the actual implementing class.

Implementing Custom Tags

Custom tags come in two basic varieties: basic tags and body tags. Basic tags don’t access their bodies (the content between the open and close tags), although they can control whether or not the JSP servlet processes their content. Body tags are just like basic tags, but have access to their body content. Support interfaces and classes for all custom tags are in the javax.servlet.jsp.tagext package.

Basic tags implement the Tag interface, which has two methods of interest to the tag developer: doStartTag() and doEndTag(). The doStartTag() method is called when the page processing reaches the opening of the tag (<enterprise:Date> in the previous example), and the doEndTag() method is called when execution reaches the end tag (</enterprise:Date>). These are called sequentially for empty tags.

The doStartTag() method can return Tag.SKIP_BODY or Tag.EVAL_BODY_INCLUDE. Skipping the body causes execution to jump right to the end of the tag. Evaluating the body will cause the JSP to process the body normally, as if it were outside of the tag. The doEndTag() method can return Tag.EVAL_PAGE or Tag.SKIP_PAGE. If EVAL_PAGE is returned, the rest of the page will be processed; if SKIP_PAGE is returned, processing stops.

Here, finally, is the DateTag class. It extends the TagSupport class, which implements the Tag interface and provides stub versions of both do- methods, as well as the other methods in the interface (which are used by the JSP to provide resources to the tag). It implements the doEndTag() method:

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
 
public class DateTag extends TagSupport {
 
  public int doEndTag() throws JspException {
 
    try {
      pageContext.getOut().println((new java.util.Date()).toString());
    } catch (java.io.IOException e) {
      throw new JspException(e);
    }
    return Tag.EVAL_PAGE;
  }
}

The pageContext object is an instance of PageContext and is provided by the TagSupport class. The PageContext object provides access to the current output writer (via getOut()), as well as the ServletRequest and ServletResponse objects for the request, page attributes, ServletConfig and ServletContext objects, and the HttpSession object.

Shown below is another TagSupport-based tag, which is a little fancier. The <enterprise:Evening> tag encloses content and displays it only if the current server time is not within the workday. Here’s how it works in a JSP:

<enterprise:Evening dayStart="9" dayEnd="17">
Sorry, we're closed for the day. Please try us tomorrow.
</enterprise:Evening>

The code is simple, but this time we extend the doStartTag() method instead and return SKIP_BODY if the current time is within the working day:

import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
 
public class EveningTag extends TagSupport {
  private int dayStart = 9;
  private int dayEnd = 17; // 24 hour time 
  // In production, we should propagate errors with exceptions
  public void setDayStart(String value) {
    dayStart = Integer.parseInt(value);
  }
  public void setDayEnd(String value) {
    dayEnd = Integer.parseInt(value);
  }
 
  public int doStartTag() throws JspException {
    int hour = GregorianCalendar.getInstance().get(Calendar.HOUR_OF_DAY);
 
    if((hour >= dayStart) && (hour <= dayEnd))
      return Tag.SKIP_BODY;
 
    return Tag.EVAL_BODY_INCLUDE;
  }
}

Note that we also have two optional tag attributes: dayStart and dayEnd, which accept an hour value, defaulting to 9 A.M. to 5 P.M. These values are communicated to the Tag via JavaBean-style setter objects. The JSP implementation will call the setters for every supplied attribute before calling any of the do XXX () methods. Attributes also need to be declared in the tag library descriptor. Here’s the extension to the previous tag library for the Evening tag:

    <tag>
        <description>Display text if it's the evening</description>
        <name>Evening</name>
        <tag-class>com.oreilly.jent.jsp.EveningTag</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>dayStart</name>
            <required>false</required>
        </attribute>
        <attribute>
            <name>dayEnd</name>
            <required>false</required>
        </attribute>
    </tag>

More About the Tag Lifecycle

JSP tags are frequently called upon to perform an action more than once. The JSP specification supports this capability via the IterationTag interface, which adds an additional lifecycle method, doAfterBody(). The doAfterBody() method is invoked after the tag body has been processed and can return IterationTag.EVAL_BODY_AGAIN to have the tag body reprocessed. The tag body is reprocessed immediately, and the doAfterBody() method is called again. Returning Tag.SKIP_BODY stops the loop.

The next example creates a tag named <enterprise:Loop>, which processes its content a given number of times (specified via the times attribute).

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
 
public class ForTag extends TagSupport {
  private int current = 1;
  private int times = 0;
 
  public void setTimes(int i) {
    times = i;
  }
 
  public int doStartTag() throws JspException {
     pageContext.setAttribute("current", new Integer(current));
     if(current < times)
       return Tag.EVAL_BODY_INCLUDE;
 
  returnTag.SKIP_BODY;
  }
 
  public int doAfterBody() throws JspException {
    if(current >= times)
      return Tag.SKIP_BODY;
    current ++;
    pageContext.setAttribute("current", new Integer(current));
    return IterationTag.EVAL_BODY_AGAIN;
  }
}

This tag also uses the setAttribute() method to create a variable in the page context named current. This allows us to see the loop count in our JSP body. Here’s an example:

<enterprise:Loop times="4">
Loop <%= current %> <br>
</enterprise:Loop>

The output in the browser will be:

Loop 1
Loop 2
Loop 3
Loop 4

We can’t just create variables at will, though. At the minimum, they have to be described in the tag library descriptor, so that the JSP compiler can create the appropriate references. Here’s the descriptor for the Loop tag:

<tag>
  <name>Loop</name>
  <tag-class>com.oreilly.jent.jsp.ForTag</tag-class>
  <description>For Loop</description>
  <variable>
    <name-given>current</name-given>
    <variable-class>java.lang.Integer</variable-class>
    <scope>NESTED</scope>
  </variable>
  <attribute>
    <name>times</name>
    <required>true</required>
    <rtexprvalue>true</rtexprvalue>
  </attribute>
</tag>

The <variable> tag declares the current variable as a java.lang.Integer. Its scope is defined as NESTED, which means it will be available to code inside the tag only. The other legal values are AT_BEGIN and AT_END. If you want to create variables dynamically (for instance, to allow the user to choose the name of the counter variable), you can create a TagExtraInfo class and associate it with the tag using <tei-class>.

Also, note the <rtexprvalue> tag associated with the times attribute. This means that the attribute can accept a JSP expression as its value. So if you have created a variable named count, the Loop tag could be opened with:

<enterprise:Loop times="<%= count %>">

Body Tags

Tags that need to interact with their body content extend the BodyTag interface instead of the Tag interface. BodyTag introduces a new method, doInitBody(), which runs before the tag body is first processed. It also introduces the BodyContent object, which provides access to the tag body. BodyContent is an extension of JspWriter, with additional methods to retrieve its content in various ways, including as a String object. The BodyTagSupport class fills the same role for BodyTag that TagSupport does for Tag and IterationTag. All BodyTag objects are also IterationTag objects.

The next example shows a simple tag that retrieves the body content, processes it through the java.net.URLEncoder object, and outputs the result:

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
 
public class EncodeTag extends BodyTagSupport {
 
  public int doAfterBody() throws JspException {
    BodyContent c = getBodyContent(); // Get the tag body
    JspWriter out = getPreviousOut(); // Get the enclosing writer
    try {
      out.print(java.net.URLEncoder.encode(c.getString()));
    } catch(java.io.IOException e) {
      throw new JspException(e);
    }
    return BodyTag.SKIP_BODY; // Do not process again
  }
}

The getPreviousOut() method of BodyTagSupport retrieves the JspWriter associated with the tag’s parent. This might be the main JspWriter for the page, or it might be the BodyContent object of another JSP tag.

Intertag Communication

As just mentioned, it’s possible for JSP custom tags to be nested within other JSP custom tags. The inner tags are executed between the doStartTag() and doEndTag() methods of the outer tags. Let’s imagine an <enterprise:Email> tag. In the JSP file, it might be used like this:

<enterprise:Email>
<enterprise:EmailAddress addr="from">deb@company.com
</enterprise:EmailAddress>
<enterprise:EmailAddress addr="to">will@company.com
</enterprise:EmailAddress>
<enterprise:EmailText>Where's that JSP Chapter?</enterprise:EmailText>
</enterprise:Email>

The Email tag is implemented with either a Tag or a BodyTag, with JavaBean-style methods to set from and to addresses and email content. The doEndTag() method assembles and sends the email. Assuming that the doStartTag() method of the email tag class doesn’t stop the tag’s processing, each of the three interior tags will be processed in turn. These tags can access the parent tag using the findAncestorWithClass() method, which takes a Tag and a class reference and finds the first appearance of the specified class in the tag’s hierarchy, working up from the specified tag.

Here’s the code for the EmailAddress tag, which sets the to and from addresses of the EmailTag class (not included):

import javax.servlet.jsp.tagext.*;
 
public class EmailAddressTag extends BodyTagSupport {
  String addrType = null;
  public void setAddr(String s) {
    addrType = s;
  }
  public int doEndTag() {
    Tag t = findAncestorWithClass(this, enterprise.EmailTag.class);
    if(t != null) {
      EmailTag et = (EmailTag)t;
      if(addrType.equalsIgnoreCase("to"))
        et.setToAddress(getBodyContent().getString());
      else if (addrType.equalsIgnoreCase("from"))
        et.setFromAddress(getBodyContent().getString());
    }
    return Tag.EVAL_PAGE;
  }
}

And that’s all there is to it. If the EmailAddressTag object doesn’t find an EmailTag in its parent hierarchy, it exits without taking any action.

Get Java Enterprise in a Nutshell, Third Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.