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.
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.
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
|
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>
|
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.
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.
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.
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.
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:
<demo:myTag/><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();
}
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.
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.
|
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.
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.
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.
|
Related Reading
|
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.
Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.