ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

JSP 1.2: Great News for the JSP Community, Part 2
Pages: 1, 2, 3

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:

  • Provide default values for optional attributes
  • Reset per-invocation state
  • Keep expensive resources for the lifetime of the tag handler object

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.

Pages: 1, 2, 3

Next Pagearrow