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


JSP 1.2: Great News for the JSP Community, Part 2

by Hans Bergsten, author of JavaServer Pages
11/07/2001

The latest version of the JSP specification, JSP 1.2, was released on September 17, 2001. In part one of this article, I described a number of new features that have been added to the specification. In this article, I focus on the details concerning the custom tag handler API.

Two new interfaces

Custom tag developers now have two new interfaces to play with: IterationTag and TryCatchFinally.

The IterationTag interface is implemented by a tag handler that repeatedly evaluates its element body. It is located between the Tag and the BodyTag interfaces in the inheritance hierarchy: IterationTag extends Tag and BodyTag extends IterationTag. A tag handler that needs full control over error conditions may implement the TryCatchFinally interface.

The following sections describe both interfaces in detail.

IterationTag interface

The IterationTag interface contains only one method:

public int doAfterBody(): Called by the container after it has processed the action element's body.

If you think this method looks familiar, you're right. It used to be part of the BodyTag interface. By moving this method to the new IterationTag interface, iterators that do not need access to the element body can be implemented without the overhead associated with maintaining a BodyContent instance.

The doAfterBody() method can return either EVAL_BODY_AGAIN (to iterate over the body) or SKIP_BODY (to stop the iteration). The EVAL_BODY_AGAIN constant is new, replacing the confusingly-named EVAL_BODY_TAG constant used in JSP 1.1.

Here's an example of a tag handler class that implements the IterationTag interface:

package com.foo;

import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyLoopTag extends TagSupport {
    private Iterator iterator;
    private String items;
    private String var;

     public void setItems(String items) {
        this.items = items;
    }

    public void setVar(String var) {
        this.var = var;
    }

    public int doStartTag() throws JspTagException {
        Collection coll = (Collection)
    pageContext.findAttribute(items);
 if (coll == null) {
     throw new JspTagException("No collection with name "
        + items + " found");
 }
 iterator = coll.iterator();
 if (iterator.hasNext()) {
     pageContext.setAttribute(var, iterator.next());
     return EVAL_BODY_INCLUDE;
 }
 else {
     return SKIP_BODY;
 }
    }

     public int doAfterBody() {
        if (iterator.hasNext()) {
     pageContext.setAttribute(var, iterator.next());
     return EVAL_BODY_AGAIN;
 }
 else {
     return SKIP_BODY;
 }
    }
}

MyLoopTag extends TagSupport. The TagSupport class in JSP 1.2 implements the IterationTag interface instead of the Tag interface and provides default implementations for the methods in both interfaces. To implement an iteration tag, you must override the doStartTag() and the doAfterBody() methods.

Related Reading

JavaServer PagesJavaServer Pages
By Hans Bergsten
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

In MyLoopTag, the doStartTag() method first retrieves the Collection specified by the items attribute and creates an Iterator for it. If the Iterator contains at least one element, the method makes the first element in the Collection available as a page scope object, with the name specified by the var attribute and returns EVAL_BODY_INCLUDE. This tells the container to add the contents of the loop element's body to the response and call doAfterBody().

The doAfterBody() does the same as doStartTag(), except that it doesn't initialize the Iterator. As long as the Iterator contains at least one more element, doAfterBody() returns EVAL_BODY_AGAIN. When all elements have been processed, it returns SKIP_BODY to stop the iteration.

You can use the loop tag with a Collection that contains beans with firstName and lastName properties like this:

<%@ taglib uri="/demolib" prefix="demo" %>
...
<ul>
  <demo:myLoopTag items="myCollection" var="current">
    <li>
<jsp:getProperty name="current" property="lastName" />,
<jsp:getProperty name="current" property="firstName" />
  </demo:myLoopTag>
</ul>

TryCatchFinally interface

The TryCatchFinally interface is a so-called "mix in interface," which means that it is intended to be implemented by a tag handler in addition to one of the other tag interfaces. It has two methods:

public void doCatch(Throwable): Called by the container if the element's body or any of doStartTag(), doEndTag(), doInitBody(), or doAfterBody() throws a Throwable. The method can rethrow the Throwable, or a different exception, after handling the problem.

public void doFinally(): Always called by the container, after doEndTag() in case of normal execution or after doCatch() in the exception case.

This new interface lets you develop tag handlers that are more robust than what is possible with just the main interfaces. For instance, a tag handler that uses a pooled resource (like a Connection from a connection pool) must have a failsafe way of returning the resource to the pool. JSP 1.1 did not guarantee that any of the tag interface methods was called in case of an exception in the element's body.

With the TryCatchFinally interface, you can return the resource to the pool in the doFinally() method and be assured that no resources fall through the cracks. For an example of how to use this interface, see the Reset per-invocation state section.

String literal attribute conversion to object

A subtle, but interesting, addition in JSP 1.2 is found in the set of rules for automatic conversion of string attribute values into other data types. These rules apply to standard actions and custom actions alike. The automatic conversion lets you to implement an attribute setter method in your tag handler with an appropriate Java data type, such as int for a numeric attribute, while still allowing the page author to set the value as a static string. The same attribute value can also be set as a request-time statement that evaluates to the same type as used by the attribute setter method:

<demo:myAction aNumber="10" />
<demo:myAction aNumber="<%= 5 + 5 %>" />

JSP 1.1 defined rules for converting a string value to any numeric data type as well as boolean and byte types, but a notable omission was a rule for converting a string value to an Object. JSP 1.2 adds this rule. One way to take advantage of this addition is to allow the same custom action attribute to be used to specify input either as the data itself or as a string that describes where the data can be found.

Say that you want to make the loop action described in the IterationTag Interface section more flexible. If you change the data type for the items attribute to Object, a request-time statement can be used to set it to either a Collection or an array. Thanks to the new conversion rule, the items attribute value can also be set as a static string that the tag handler interprets as the name of a Collection or array saved in one of the JSP scopes (the only option in the previous version of the action). The new version of the loop action can be used like this:

<%-- With the name of a Collection or array --%>
<demo:myLoopTag items="myCollection" var="current">
  ...
</demo:myLoopTag>

<%-- With a Collection or array scripting variable --%>
<demo:myLoopTag items="<%= aScriptingVar %>" var="current">
  ...
</demo:myLoopTag>

To make this work, you first need to change the data type for the items attribute in the MyLoopTag tag handler class:

private Object items;

public void setItems(Object items) {
    this.items = items;
}

Instead of a String attribute, items is now of type Object.

The doStartTag() method must be modified to handle the different types of objects that can be used as the value of the items attribute. Here I use a new method, toIterator(), that converts whatever was passed in as the items value into the Iterator we need:

public int doStartTag() throws JspTagException {
      iterator = toIterator(items);
if (iterator.hasNext()) {
    pageContext.setAttribute(var, iterator.next());
    return EVAL_BODY_INCLUDE;
      }
else {
    return SKIP_BODY;
      }
   }

Finally, the toIterator() method looks like this:

private Iterator toIterator(Object items)
    throws JspTagException {

Iterator i = null;
Object dataStructure = items;
if (items instanceof String) {
    dataStructure =
      pageContext.findAttribute((String) items);
    if (dataStructure == null) {
        throw new JspTagException("No collection with name "
          + items + " found");
    }
      }
if (dataStructure.getClass().isArray()) {
    dataStructure = Arrays.asList((Object[])
      dataStructure);
      }
if (dataStructure instanceof Collection) {
     i = ((Collection) dataStructure).iterator();
}
else {
    throw new JspTagException("Invalid data type 
      for 'items'");
}
return i;
  }

If the items attribute value is a String, the method first retrieves the object from one of the JSP scopes. The type of the retrieved object is then analyzed. If it's an array, it's turned into a List (which is a type of Collection). An Iterator is then created for the Collection and returned.

PropertyEditors for custom string literal conversion

An even more powerful addition to the attribute conversion mechanism is the ability to use a bean PropertyEditor to convert a literal string value to any Java data type. If an action attribute value is specified as a literal string for an attribute of a type other than String, the container looks for a PropertyEditor that can convert the string to the attribute's data type.

Say you have an attribute of type java.util.Date. To let the page author specify it as a string, you need a PropertyEditor that converts a String to a Date. Here's how it's done.

First you implement the PropertyEditor:

  package com.foo;

  import java.beans.*;
  import java.text.*;
  import java.util.*;
  public class MyDatePE extends PropertyEditorSupport
    implements PropertyEditor {
private SimpleDateFormat sdf =
  new SimpleDateFormat("yyyy-MM-dd");
private Date value;

public Object getValue() {
    return value;
}

public void setAsText(String text)
  throws IllegalArgumentException {
    try {
        value = sdf.parse(text);
    }
    catch (ParseException e) {
        throw new IllegalArgumentException(e.getMessage());
    }
}
  }

The container calls the setAsText() method with the attribute's String value. This method creates a Date object from the string and saves it in the instance variable named value. The container then calls the getValue() method, which returns the new Date object, and uses the value to set the action's attribute value.

Simple enough, but you must also tell the container to use your PropertyEditor for this action. You can do that by creating a BeanInfo class for the action's tag handler:

package com.foo;

import java.beans.*;
import java.util.*;
public class MyTagBeanInfo extends SimpleBeanInfo {
    public PropertyDescriptor[] getPropertyDescriptors() {
  PropertyDescriptor[] pds = new PropertyDescriptor[4];
  try {
      pds[0] =
  new PropertyDescriptor("anInt", MyTag.class,
      null, "setAnInt");
pds[1] =
  new PropertyDescriptor("aString", MyTag.class,
      null, "setAString");
pds[2] =
  new PropertyDescriptor("firstDate", MyTag.class,
      null, "setFirstDate");
pds[3] =
  new PropertyDescriptor("secondDate", MyTag.class,
      null, "setSecondDate");
  }
  catch (Exception e) {}
 
              pds[2].setPropertyEditorClass(MyDatePE.class);
  pds[3].setPropertyEditorClass(MyDatePE.class);
  return pds;
    }
}

This BeanInfo class is for a tag handler with four attributes, named anInt, aString, firstDate and secondDate. The getPropertyDescriptors() method first creates an array with one PropertyDescriptor for each attribute and then sets the property editors for the two Date attributes to the PropertyEditor class described earlier.

A BeanInfo class is automatically bound to its bean class (in this case, the tag handler class is considered to be a bean) through a class naming convention: the name of the BeanInfo class for a bean simply has the same name as the bean class plus "BeanInfo." So in this example, MyTagBeanInfo is the BeanInfo class for the MyTag class. The MyTag class is a regular tag handler class. You don't need to do anything special in the tag handler class itself in order to use a PropertyEditor to convert string values to other types.

Tag handler API clarifications

JSP 1.2 clarifies a number of details related to how tag handler classes must be handled by the container. JSP 1.1 was a bit sketchy on the subject, and in fact contradicted itself in a few places. The clarifications were therefore greatly needed, and will result in better portability for custom tag libraries between containers and allow container vendors to improve performance by reusing tag handler instances.

Empty element semantics

The first area that has been clarified is what constitutes an empty custom action element and how a container must deal with it. An action element is considered to be empty if it's:

  1. Represented by the XML shorthand notation for an empty element:
    <demo:myTag/>
  2. Represented by an opening and closing tag but with an empty body:
    <demo:myTag></demo:myTag>

Note that if the body contains anything, even so-called whitespace characters (blank, tab, line feed) or scripting elements, the element is not considered to be empty.

For an empty custom action element with a tag handler that implements the BodyTag interface, the container does not call the following methods: setBodyContent(), doInitBody(), and doAfterBody(). This allows the container to generate more efficient code for an empty BodyTag element than for a BodyTag element with a body, since it doesn't have to create a BodyContent instance.

Some JSP 1.1 containers already deal with empty action elements this way, but others treat empty and non-empty elements the same. If you have only tested your custom action in a JSP 1.1 container that always calls the BodyContent-related methods, you may run into problems when you use it in a JSP 1.2 container. A typical mistake is to assume that the tag handler always has access to a BodyContent instance and use code like this to get hold of the proper JspWriter:

JspWriter out = bodyContent.getEnlosingWriter();

With the clarified rules, this code will throw a NullPointerException if the custom action is used without a body. You should always check for null with code like this, instead:

JspWriter out = null;
if (bodyContent != null) {
    out = bodyContent.getEnclosingWriter();
}
else {
    out = pageContext.getOut();
}

Tag handler life cycle and instance reuse

Another area that's been clarified is the tag handler life cycle. This allows vendors to implement reuse strategies for tag handler instances, resulting in better performance without sacrificing portability.

The tag handler life cycle details are pretty complex, and mostly of interest to container developers. But briefly, for a tag handler that implements just the Tag interface, all setter methods (setPageContext(), setParent(), and all setters for attributes) are called first. Then the doStartTag() method is called. The doEndTag() method is called if no exception is thrown by doStartTag() or while processing the element's body. The tag handler instance may then be reused for another occurrence of the custom action that uses the same set of attributes, with the same or different values, in the same or a different page. If an attribute for the other occurrence has a different value, the corresponding setter method is called, followed by the doStartTag()/doEndTag() calls as before. Eventually, the container is ready to get rid of the tag handler instance. At this point, it calls the release() method to let the tag handler release internal resources it may have used.

Let's look at what this means from a tag developer's perspective. There are a number of things you may need to do in your tag handler, for instance:

The following sections describe the requirements the tag handler life cycle places on you to get this right.

Provide default values for optional attributes

If some attributes are optional, you must provide default values for the attributes. You can do so in a number of ways; for instance, in the variable declaration, or through a getter method used by other tag handler methods:

private int optionalInt = 5;
private java.util.Date optionalDate;

private java.util.Date getOptionalDate() {
     if (optionalDate == null) {
     return new java.utl.Date();
  }
else {
    return optionalDate;
  }
}

Given that the tag handler instance may be reused for another occurrence of the custom action, you may think that you need to reset the attributes to their defaults before this happens. But that is not the case. Look at the description of the life cycle again. A tag handler instance can only be reused for an occurrence with the same set of attributes. Put another way, if a tag handler instance is used for an occurrence that does not use an optional attribute, it can only be reused for other occurrences that also omit this attribute. The default value will never need to be reset; it's never set for any of the occurrences that use the instance in the first place.

Reset per-invocation state

A tag handler may create or collect data that is only valid for one invocation. One example is a list of values set by custom actions nested in the body of the main action:

<demo:redirect>
  <demo:param name="foo" value="bar" />
  <demo:param name="fee" value="baz" />
</demo:redirect>

In this example, the nested param actions call a method in the tag handler for the parent redirect action to add the parameter to a list that is then used in the redirection URL:

private ArrayList params;
 ...
public void addParam(Param param) {
    if (params == null) {
    params = new ArrayList();
    }
params.add(param);
}

If the container decides to reuse this tag handler, the list will grow for each invocation unless you reset it at some point. There's no guarantee that the doEndTag() method is called (in case of an exception in the body), so the best place to reset the list is in the doStartTag() method:

public int doStartTag() throws JspException {
  // Reset per-invocation state
  params = null;
   ...
}

This approach works fine for objects that can hang around until the tag handler is used again. But what if you need to use an expensive resource, such as a database connection, that must be released (or returned to a pool) as soon as possible? That's when the new TryCatchFinally interface comes in handy. Say you have a custom action that provides a database connection for a set of nested custom actions that performs database operations that must be executed as part of the same database transaction:

<demo:sqlTransaction dataSource="myDataSource">
  <demo:sqlUpdate>
    UPDATE Account SET Balance = Balance - 1000
WHERE AccountNumber = 1234
  </demo:sqlUpdate>
  <demo:sqlUpdate>
    UPDATE Account SET Balance = Balance + 1000
WHERE AccountNumber = 5678
  </demo:sqlUpdate>
</demo:sqlTransaction>

Here the tag handler for the sqlTransaction action gets a Connection from the specified DataSource and makes it available to the nested actions through a getter method. If any exception occurs while processing a nested action, the sqlTransaction tag handler must roll back the transaction. No matter what happens, the tag handler must always close the Connection (returning it to the pool, if it's a pooled DataSource).

By implementing the TryCatchFinally interface for the sqlTransaction tag handler, this is easy to accomplish:

public class SqlTransactionTag extends TagSupport,
    implements TryCatchFinally {
    
      private java.sql.Connection conn;
 ...
public void doCatch(Throwable t) throws Throwable {
    conn.rollback();
    throw t;
}

public void doFinally() {
    // Close or return to pool if pooled
    conn.close();
  }
}

The doCatch() method rolls back the transaction and doFinally() closes the Connection, no matter if an exception was thrown or not.

Keep expensive resources for the lifetime of the tag handler object

Some objects used by a tag handler can be expensive to create, such as a java.text.SimpleDateFormat instance or an XML parser. Instead of creating objects like this every time the tag handler is invoked, it's better to create them once when the tag handler itself is created or the first time it's used. The place to get rid of objects like this is in the release() method:

private java.text.SimpleDateFormat dateFormat =
   new java.text.SimpleDateFormat();

 ...
public void release() {
    dateFormat = null;
}

The release() method is only called just before the container gets rid of the tag handler, to let it do this kind of cleanup. It is never called between invocations.

Tag Library Descriptor (TLD) changes

Besides the new elements for validators and listeners described earlier, JSP 1.2 introduces a number of other changes and additions to the TLD.

First of all, some elements have been renamed for consistency with the naming conventions used in other J2EE descriptor files. More precisely, hyphens are now used to separate words in element names, and the <info> element has been replaced with an element used for the same purpose in other descriptors. The following table summarizes these name changes:

JSP 1.1 JSP 1.2
<tlibversion> <tlib-version>
<jspversion> <jsp-version>
<shortname> <short-name>
<info> <description>
<tagclass> <tag-class>
<teiclass> <tei-class>
<bodycontent> <body-content>

A number of new elements have also been added to allow more descriptive information in the TLD. This information may be used by page-authoring tools and also by tools that generate user documentation from the TLD:

New element Description
<display-name> Used to specify a short name displayed by tools. This element can be used as a subelement of the <taglib> and <tag> elements.
<small-icon> and <large-icon> Used to specify icons displayed by tools. The values are paths to files containing either GIF or JPEG images. Both elements can be used as subelements of the <taglib> and <tag> elements.
<example> Used to include an example of how a tag can be used. This element can only be used as a subelement of the <tag> element.
<type> Used to specify the type of a request-time attribute value. This element can only be used as a subelement of the <attribute> element.

Finally, the new <variable> element can be used to declare scripting variables introduced by a tag. In JSP 1.1, you had to create a TagExtraInfo class for the tag and let the getVariableInfo() method return information about the variables. By using the new <variable> element, you can avoid creating a TagExtraInfo class for this purpose in most cases:

<tag>
  <name>myTag</name>
  <tag-class>com.foo.MyTag</tag-class>

  <variable>
    <name-given>foo</name-given>
    <variable-class>String</variable-class>
    <declare>true</declare>
    <scope>AT_END</scope>
    <description>This variable contains ...</description>
  </variable>
    ...
</tag>

In this example, the myTag custom action introduces a scripting variable named foo of type String. The container is asked to create a variable declaration (declare is true) for this variable after the end tag (scope is AT_END). The other valid values for the <scope> element are AT_BEGIN and NESTED, exactly the same as in a TagExtraInfo class.

As an alternative to declaring a hard-coded variable name, like foo in this example, you can let the variable name be specified by the page author through an action element attribute. To do so, replace the <name-given> element with the <name-from-attribute> element:

<variable>
  <name-from-attribute>id</name-from-attribute>
   ...
</variable>

With this declaration, the container uses the value of the id attribute as the variable name instead.

Conclusion

Related Reading

JavaServer PagesJavaServer Pages
By Hans Bergsten
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

In this article I have described JSP 1.2's most important new features, fine-tuning, and clarifications. I have also showed you examples of how to use the new stuff and what to watch out for. You're now ready to try it out on your own. I suggest you download Tomcat 4.0 and play around with it to see how you can best take advantage of JSP 1.2 in your own application.

Resources


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.