Advanced Features of JSP Custom Tag Libraries
by Sue Spielman01/18/2001
In this article, the second in the JSP custom tag libraries series, we will cover advanced JSP features and how to use them. If you aren't familiar with tag libraries, spend a few minutes reading the previous article, Designing JSP Custom Tag Libraries.
In this article you'll learn about
- JSP container interaction with tags,
- tags with bodies,
- nested tags,
- tag extra info, and
- cooperating tags.
All of the classes and interfaces discussed herein are from the
javax.servlet.jsp or
javax.servlet.jsp.tagext package.
The JSP container interaction with tags
When using advanced features of tag libraries it is important to have a basic understanding of how the JSP container interacts with the Tag handler classes. We'll look at this first before moving to using advanced features.
Tag Interface
The Tag interface defines the basic protocol
between a tag handler and JSP container. It defines the life cycle and the
methods to be invoked when the start and end tag of an action are
encountered.
The JSP container invokes the setPageContext,
setParent, and attribute setting methods before calling
doStartTag. The container also guarantees that release will be
invoked on the tag handler before the end of the page. A typical tag
handler method invocation sequence from the JSP container might look
like:
SomeTag tag = new someTag();
tag.setPageContext(...);
tag.setParent(...);
tag.setAttribute1(value1);
tag.setAttribute2(value2);
tag.doStartTag();
tag.doEndTag();
tag.release();
The release method of tag handler should reset its state and release any private resources that it might have used. Usually it isn't necessary to override the base class implementation.
Body Tag Interface
If a tag is derived from
BodyTagSupport, there are additional methods --
setBodyContent, doInitBody, and
doAfterBody -- contained in the BodyTag interface. The
additional methods let a tag handler access its body. The body of a
tag is anything that comes between the start and end of a tag.
The BodyContent object is a subclass of
JspWriter, which is the writer used internally for the
JSP out variable. The BodyContent object is available
through the bodyContent variable in
doInitBody, doAfterBody, and
doEndTag. This is important because the BodyContent
object contains methods that you can use to write, read, clear, and
retrieve content and then incorporate that content into the original
JspWriter during the doEndTag. We'll show
this later when we go through a code sample.
The setBodyContent creates a body content and adds it to the
tag handler. The doInitBody is called only once before the
evaluation of the tag body and is typically used to perform any
initialization that depends on the body content. The
doAfterBody is called after each evaluation of the tag body. If
doAfterBody returns EVAL_BODY_TAG, the entire body
is evaluated again. Otherwise SKIP_BODY is returned to indicate
that the tag is finished with its body evaluation.
A typical tag handler derived from BodyTagSupport would have
the following method invocation sequence from the JSP container:
tag.doStartTag();
out = pageContext.pushBody();
tag.setBodyContent(out);
// perform any initialization needed after body content is set
tag.doInitBody();
tag.doAfterBody();
// while doAfterBody returns EVAL_BODY_TAG we
// iterate the body evaluation
...
tag.doAfterBody();
tag.doEndTag();
tag.pageContext.popBody();
tag.release();
Using tags with bodies
Using a tag that evaluates a body allows for looping. This can be a
powerful feature for dealing with database result sets or other types
of data that are enumerations. When defining a tag that uses a body,
there are two basic steps. First the tag definition in the TLD file
needs to set <bodyContent> to either JSP or
tagdependent. JSP indicates that the body is evaluated by the JSP
container and then is possibly processed by the tag
itself. Tagdependent indicates that the body is only processed by the
tag. The TLD element looks like
<tag>
...
<bodycontent>JSP|tagdependent</bodycontent>
</tag>
The second step is to derive the tag from BodyTagSupport.
BodyTagSupport is the helper class of the BodyTag interface, so
technically you don't have to implement any of the methods since there are
default implementations. However it's more than likely that you will want to
override one or all of the methods to do specific processing for your
tag.
How one implements a tag handler for a tag with a body depends on
whether the tag handler needs to interact with the body or not, where
"interact" means that the tag handler reads or modifies the contents
of the body or causes iterative evaluations of the body. If the tag
handler interacts with the body, the return value
SKIP_BODY indicates that the JSP container should not
evaluate the code within the body of the tag. The
EVAL_BODY_TAG value is only possible if the class extends
BodyTagSupport. If the tag handler does not need to
interact with the body, the tag handler should implement the Tag
interface (or be derived from TagSupport). If the body of
the tag needs to be evaluated, the doStartTag method must
return EVAL_BODY_INCLUDE; otherwise it should return
SKIP_BODY. If the tag handler needs to interact with the
body, the tag handler must implement BodyTag (or be derived from
BodyTagSupport). Such handlers typically implement the
doInitBody and the doAfterBody
methods. These methods interact with body content passed to the tag
handler by the JSP container. The doStartTag method needs
to return EVAL_BODY_TAG; otherwise it should return
SKIP_BODY.
Let's put a quick sample together that shows tag interaction with a body. This will include the TLD tag definition, the tag handler, and finally the JSP page that calls the tag. For a complete example of the TLD file, refer to part one of this series (JSP Customer Tag Libraries).
The definition of this tag in the TLD file looks like
<tag>
<name>paramLoop</name>
<tagclass>oreilly.examples.ParamLoopTag </tagclass>
<!-- Allow for a body to be included for this tag -->
<bodycontent>JSP</bodycontent>
<info>
This is a simple tag to demonstrate how to include
and evaluate the body of a tag
</info>
<!-- Required attributes -->
<attribute>
<name>enum</name>
<required>true</required>
<rtexpvalue>true</rtexpvalue>
</attribute>
</tag>
The following sample is a tag handler that displays all of the request parameters in a table and interacts with the body:
import java.util.Enumeration;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.IOException;
public class ParamLoopTag extend BodyTagSupport{
Enumeration enum=null;
public void setEnum(Enumeration enum){
this.enum = enum;
}
public int doStartTag() throws JspException{
return EVAL_BODY_TAG;
}
public void doInitBody() throws JspException{
if (enum != null){
if (enum.hasMoreElements()){
pageContext.setAttribute("nextParamName",
enum.nextElement());
}
}
public void doAfterBody() throws JspException{
if (enum.hasMoreElements()){
pageContext.setAttribute("nextParamName",
enum.nextElement());
//Continue looping
return EVAL_BODY_TAG;
} else {
// We're done
return SKIP_BODY;
}
}
public int doEndTag() throws JspException{
try {
// Get the current bodyContent for this tag and
//write it to the original JSP writer
pageContext.getOut().print(bodyContent.getString());
return EVAL_PAGE;
}
catch (IOException ioe){
throw new JspException(ioe.getMessage());
}
}
}
The JSP code that uses this tag might look like
<html>
<body>
<%@ taglib uri="/oreillySample.tld" prefix="sample" %>
<h1>Sample Tag using Body Interaction</h1>
<table border="2">
<tr>
<th>Parameter Name</th>
<th>Parameter Value</th>
<sample:paramLoop enum="<%=request.getParameterNames() %>" >
<tr>
<%--
For simplicity, we are not doing any error
checking here, and just printing the first value.
--%>
<% String paramName =
(String)pageContext.getAttribute("nextParamName"); %>
<td> <%= paramName %> </td>
<% String[] paramValues =
request.getParatmerValues(paramName); %>
<td><%= paramValues[0] %>
</tr>
</sample:paramLoop>
</table>
</body>
<html>
The table output produced from this tag executing assuming two incoming parameters (person and company) would look like
| Parameter Name | Parameter Value |
| person | Sue |
| company | Switchback Software |
Pages: 1, 2 |