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 ofdoStartTag(),doEndTag(),doInitBody(), ordoAfterBody()throws aThrowable. The method can rethrow theThrowable, or a different exception, after handling the problem.
public void doFinally(): Always called by the container, afterdoEndTag()in case of normal execution or afterdoCatch()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:
- Represented by the XML shorthand notation for an empty element:
<demo:myTag/> - 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.