ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


JavaServer Pages, 3rd Edition

JSP 2.0: The New Deal, Part 4

by Hans Bergsten, author of JavaServer Pages, 3rd Edition
05/12/2004

In this final part of the "JSP 2.0: The New Deal" series, we look at two new features that make it much easier to develop custom tag libraries: tag files and the new simplified tag-handler Java API.

Developing Custom Actions as JSP Tag Files

JSP is intended to make it possible for people who are not Java gurus to write pages with dynamic content. Reusing a piece of dynamic, complex content between pages has been a bit of a pain until now. Say that you want to put a quick poll on a number of pages, with the question and answers picked up from variables. Prior to JSP 2.0, you had three options. You could copy and paste the poll form to every page where you wanted it. A better choice was to write a separate JSP page that generates the poll form, and include it in other pages with the <jsp:include> or <c:import> actions, but you could only pass it input parameters of type String, not a bean or a Map holding the answers. for instance. The third choice was to implement a custom action as a Java tag handler class, but then you'd need to know Java.

JSP 2.0 adds a fourth option: namely, developing a custom action as a tag file. A tag file is a plain text file where you use JSP elements for all dynamic parts, just as in a regular JSP page. It has the same purpose as a Java tag handler: to provide the logic for a custom action. The main differences between a tag file and a JSP page are that a tag file has a .tag filename extension, uses a tag directive instead of a page directive, and lets you declare input and output with a few new directives that are only valid in tag files.

Let's take a closer look. Here's a tag file named poll.tag that generates a quick poll form:

<%@ tag body-content="empty" %>
<%@ attribute name="question" required="true" %>
<%@ attribute name="answers" required="true" 
   type="java.lang.Object" %>
<%@ attribute name="votesMapName" required="true" %>
<%@ attribute name="answersMapName" required="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

Question: ${question}<br>
<form action="result.jsp" target="result">
   <input type="hidden" name="question" value="${question}">
   <input type="hidden" name="votesMapName" value="${votesMapName}">
   <input type="hidden" name="answersMapName" value="${answersMapName}">
   <c:forEach items="${answers}" var="a">
      <input type="radio" name="vote" value="${a.key}">${a.value}<br>
   </c:forEach>
   <input type="submit" value="Vote">
</form>

Related Reading

JavaServer Pages
By Hans Bergsten

At the top of the file, there's a tag directive. The tag directive is similar to the page directive you use in a JSP page; it declares general file characteristics. Here I use the body-content attribute to declare that an action element representing this tag file in a JSP page must be empty; i.e., the action element must not have a body. Instead of empty, you can use the values scriptless (the body can contain anything except scripting elements), or tagdependent (the container passes the body to the tag handler without evaluating it). If you've developed custom actions as Java classes, you probably recognize the body-content and the valid values from the Tag Library Descriptor (TLD) used to declare Java tag handlers. Because a tag file doesn't need to be declared in a TLD, the tag directive and other special tag file directives are used to provide the same type of information that the TLD provides to the JSP container.

The attribute directive that follows the tag directive in this example serves the same function as the TLD element with the same name: it declares valid custom action element attributes. The poll tag file accepts four attributes:

There's one attribute directive per action element attribute, with the name declared by the name attribute. All action element attributes in this example are required, as declared by the required attribute for each attribute directive. The answers attribute value must be a Map, which is declared by the type attribute. All other attributes must be of type String, which is the default type, so I don't specify the type attribute for them.

The rest of the tag file is plain old JSP. A taglib directive declares that the JSTL core library is used in the file, and the body of the file uses the attribute values (available to the tag file as page-scope variables) in an EL expression to write the question and a form with radio buttons for each answer. The answers and votes Map variable names and the question are encoded as hidden fields in the form so they get passed on to the page processing the vote, where they may be used like this:

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
   <head>
      <title>Poll Results</title>
   </head>
   <body bgcolor="white">
      <c:set target="${applicationScope[param.votesMapName]}" 
        property="${param.vote}" 
        value="${applicationScope[param.votesMapName][param.vote] + 1}" />
    
      <p>
        Question: ${param.question}<br>
        <c:forEach items="${applicationScope[param.answersMapName]}"
          var="a">
          ${a.key}) ${a.value}: 
          ${applicationScope[param.votesMapName][a.key]}<br>
        </c:forEach>
      </p>
   </body>
</html>

It's a regular JSP page, using JSTL actions. It increments the value for the key matching the selected poll answer in the Map holding vote results, available in the application scope under the name provided through the votesMapName parameter. Next, it writes the question and iterates through the Map with poll answers, available in the application scope under the name provided by the answersMapName, and writes each answer along with the current number of votes for the answer.

Of more interest than how to process the poll votes is how to use the custom action implemented by the poll tag file in a JSP page. Here's an example:

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %>

<html>
   <head>
      <title>My Page</title>
   </head>
   <body bgcolor="white">
      <jsp:useBean id="myAnswers" scope="application" 
        class="java.util.TreeMap">
        <c:set target="${myAnswers}" property="1" value="Yes" />
        <c:set target="${myAnswers}" property="2" value="No" />
        <c:set target="${myAnswers}" property="3" value="Maybe" />
      </jsp:useBean>
      <jsp:useBean id="myVotes" scope="application"
        class="java.util.HashMap" />    
      ...
      <p>
        <my:poll question="Will you start using tag files?" 
          answers="${myAnswers}"
          answersMapName="myAnswers" votesMapName="myVotes" />
      </p>
      ...
   </body>
</html>

The first thing to notice is the taglib directive that declares the tag library holding the tag file. To use a tag file without creating a TLD, you must store the tag file somewhere under the WEB-INF/tags directory. The example poll.tag is stored in the WEB-INF/tags/mytags directory, and I use this directory name as the value of the taglib directive's tagdir attribute. This tell the container that all tag files found in this directory belong to the same tag library, and that the action elements for actions in this library are identified by the prefix specified by the taglib directive's prefix attribute; my, in this example. Alternatively, you can package tag files along with a TLD in a .jar file, and declare the library with the same kind of taglib directive that you use for a regular custom tag library; i.e., with a uri attribute instead of the tagdir.

In this example, the Map objects for answers and vote counts are created by <jsp:useBean> actions and populated with JSTL <c:set> actions, but you can, of course, create them any way you like (e.g., in a context listener or in a "plugin" class in an Apache Struts application). No matter how it is created, the tag file is invoked through a regular JSP custom action element. I use an EL expression that evaluates to the answers Map as the value of the answers attribute. Tag file attributes accept EL expressions by default, but you can declare that a static value must be specified with the rtexprvalue attribute of the attribute directive.

When you request this page, the JSP container processes it as usual, locating the implementation for the <my:poll> custom action element with the help of the element name prefix and the taglib directive. The container may process the tag file any way it wants; the Tomcat 5 container converts it into a Java tag handler class, compiles it, and executes it.

Processing the Custom Action Body

Just like a tag handler class written in Java, a tag file can ask the container to evaluate the custom action element body and, optionally, further process the evaluation result.

Let's make it possible to add a short description of the poll question in the action element body, like this:

<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %>
...
<html>
   ...
   <body bgcolor="white">
      ...
      <p>
        <my:poll question="Will you start using tag files?" 
          answers="${myAnswers}"
          answersMapName="myAnswers" votesMapName="myVotes" >
          JSP 2.0 introduces a new way to develop custom action
          tag handlers, called <i>tag files</i>
        </my:poll>
      </p>
      ...
   </body>
</html>

The body in this example only contains text, but it could also contain action elements and EL expressions. To evaluate the body and add the result to the response, we need to modify the poll tag file like this:

<%@ tag body-content="scriptless" %>
<%@ attribute name="question" required="true" %>
<%@ attribute name="answers" required="true" 
   type="java.lang.Object" %>
<%@ attribute name="votesMapName" required="true" %>
<%@ attribute name="answersMapName" required="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<p>
   <jsp:doBody/>
</p>
Question: ${question}<br>
<form action="result.jsp" target="result">
   <input type="hidden" name="question" value="${question}">
   <input type="hidden" name="votesMapName" value="${votesMapName}">
   <input type="hidden" name="answersMapName" value="${answersMapName}">
   <c:forEach items="${answers}" var="a">
      <input type="radio" name="vote" value="${a.key}">${a.value}<br>
   </c:forEach>
   <input type="submit" value="Vote">
</form>

First we change the tag directive body-content attribute value to scriptless. As I mentioned earlier, this means that the body can contain any kind of content except scripting elements. Next, we add a <jsp:doBody> action, which tells the container to evaluate the body and add the result to the response. Alternatively, you can use the var attribute, capture the evaluation result, and process it further.

In addition to the features I've described here, a tag file can return information back to the invoking file through variables, access undeclared attributes, and have fragment attributes (i.e., attributes holding action elements and EL expressions that can be evaluated by the tag file in a way similar to how it evaluates the action element body). You can read all about these features in Chapter 11 of my JSP book, which is the sample chapter available online.

A Simpler, Java Tag-Handler API

Being able to write a custom action tag handler as a tag file is a great new feature, especially for custom actions that generate a lot of HTML. But some things are hard to do with just JSP actions and EL expressions, so the Java tag-handler API is still needed. Prior to JSP 2.0, writing a tag handler in Java could be quite complex, due to the tricky interaction between the container and the tag handler needed to process the action element body. This complexity is required in order to support Java scripting elements in the action element body. However, if the body contains only template text, EL expressions, and action elements, a much simpler API can be designed. That's exactly what was done for JSP 2.0, and it's appropriately named the simple tag handler API.

Here's a Java tag handler for the poll custom action we implemented as a tag file earlier:

package com.mycompany.mylib;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class PollTag extends SimpleTagSupport {
   private String question;
   private Map answers;
   private String votesMapName;
   private String answersMapName;

   public void setQuestion(String question) {
      this.question = question;
   }

   public void setAnswers(Map answers) {
      this.answers = answers;
   }

   public void setVotesMapName(String votesMapName) {
      this.votesMapName = votesMapName;
   }

   public void setAnswersMapName(String answersMapName) {
      this.answersMapName = answersMapName;
   }

   public void doTag() throws JspException, IOException {
      JspWriter out = getJspContext().getOut();
      JspFragment body = getJspBody();
      if (body != null) {
         out.println("<p>");
         body.invoke(null);
         out.println("</p>");
      }
      out.print("Question:");
      out.print(question);
      out.println("<br>");
      out.println("<form action=\"result.jsp\" target=\"result\">");
      out.print("<input type=\"hidden\" name=\"question\" value=\"");
      out.print(question);
      out.println("\">");
      out.print("<input type=\"hidden\" name=\"votesMapName\" value=\"");
      out.print(votesMapName);
      out.println("\">");
      out.print("<input type=\"hidden\" name=\"answersMapName\" value=\"");
      out.print(answersMapName);
      out.println("\">");
      Iterator i = answers.keySet().iterator();
      while (i.hasNext()) {
         String key = (String) i.next();
         String value = (String) answers.get(key);
         out.print("<input type=\"radio\" name=\"vote\" value=\"");
         out.print(key);
         out.print("\">");
         out.print(value);
         out.println("<br>");
      }
      out.println("<input type=\"submit\" value=\"Vote\">");
      out.println("</form>");
   }
}

A simple tag must implement the new javax.servlet.jsp.tagext.SimpleTag interface. The example tag handler does so by extending the javax.servlet.jsp.tagext.SimpleTagSupport class, which provides default implementations of all methods. Just like a classic tag handler class, you need setter methods for each custom action attribute, but there's only one processing method to implement: the doTag() method.

In the example tag handler, the doTag() method tries to get an executable representation of the action element body (a JspFragment instance) by calling the getJspBody() method inherited from the SimpleTagSupport class. If there is a body, the tag handler invokes it with a null value as the argument to the invoke() method, which means that the evaluation result is added to the response. Just as for the <jsp:doBody> action, you can capture the result instead by passing a Writer instance to the invoke() method. The doTag() method then writes the HTML for the form with radio buttons for all alternative answers, just as the tag file implementation does.

Because there's only one method that the container calls to let the tag handler do its thing, instead of three methods in a classic tag handler that process its body (doStartTag(), doAfterBody(), and doEndTag(), each with a return value telling the container what to do next), implementing a tag handler as a SimpleTag is considerably easier than with the classic tag-handler API. In addition, simple tag-handler instances are never reused, so you don't have to worry about resetting state -- a subject that has turned out to cause a lot of problems, described in one of my earlier articles, "JSP 1.2: Great News for the JSP Community, Part 2".

You declare a simple tag handler in a TLD in exactly the same way that you declare a classic tag handler, but the <body-content> TLD element must have a value other than JSP, since scripting elements are not allowed in the element body for a custom action backed by a simple tag handler. Other than that, using a custom action implemented as a simple tag handler is no different than using one implemented as a classic tag handler.

Both simple and classic tag handlers can support undeclared attributes by implementing a new interface called javax.faces.jsp.tagext.DynamicAttributes. Attributes for both types can be of type JspFragment for attributes containing other actions or EL expressions, evaluated any number of times by the tag handler. You can read more about these features in my book JavaServer Pages, 3rd Edition.

Conclusion

In this series, I've shown you all of the new stuff that JSP 2.0 adds: the expression language, better error handling, new configuration options, improved XML page format, and two new ways to develop tag handlers. All in all, these improvements make it possible to develop JSP pages that are both easy to understand and to maintain.

If you want to try out the new JSP 2.0 features, I recommend that you use Apache Tomcat 5, one of the first JSP containers to implement the new specification. Tomcat 5 is available at the Jakarta Project site.

Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.