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
All of the classes and interfaces discussed herein are from the
javax.servlet.jsp or
javax.servlet.jsp.tagext package.
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 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 |
|
Nested tags are another powerful feature of tag libraries. You can
use nested tags whether the tag implements the Tag or BodyTag
interface. When tags are nested, it's possible to obtain a reference
to the parent class by using the findAncestorWithClass
method. By casting the reference to the parent class, the nested tag
handler can call methods in the parent class. Another use for the
findAncestorWithClass is to determine what type of tag in
which another tag might be nested. It is likely, when building more
complicated systems, that a tag handler might need to act differently
depending on what the outer tag is. The tag handler can determine
what action to take based on the return value of the
findAncestorWithClass. Nested tags really only effect the
tag handler, so I will not reiterate TLD and JSP code here.
The following sample demonstrates the concepts we just discussed:
public class OuterTag1 extends TagSupport {
…
public void setMyValue(MyClass arg) { ... }
public MyClass getMyValue() { ... }
}
public class OuterTag2 extends TagSupport {
…
public void setSomeOtherValue(MyClass arg) { ... }
public MyClass getSomeOtherValue() { ... }
}
public class InnerTag extends BodyTagSupport {
public int doStartTag() throws JspTagException {
OuterTag1 parent1 =
(OuterTag1)findAncestorWithClass(this, OuterTag1.class);
OuterTag2 parent2 =
(OuterTag2)findAncestorWithClass(this, OuterTag2.class);
if (parent1 != null) {
parent1.setMyValue(...);
}
if (parent2 != null){
// make a different method call
MyClass value = parent2.getSomeOtherValue(…);
// Take a different action here for tag
// processing…
// do something with the value
}
return(EVAL_BODY_TAG);
}
...
}
The Tag Extra Info (TEI) Class is used to enable scripting
variables and also to perform attribute validation. The TEI is a Java
class that extends the TagExtraInfo class. The method
used is the getVariableInfo; it takes a TagData parameter
and returns an array of VariableInfo. The TagData
parameter contains the name-value pairs of attributes which can be
used for scripting variables.
The main purpose of the getVariableInfo method is to
create an array of VariableInfo objects. You create one VariableInfo
object for each scripting variable that you will be using. When
new'ing a VaribleInfo object the following are
defined:
The scope of variables in tags can be defined as one of the following:
AT_BEGIN -- the variable is available in the body of
the tag an in the remainder of the JSP;NESTED -- the variable is available in the body of the
tag;AT_END -- the variable is available in the remainder
of the JSP pageOne more aspect of TEI worth mentioning is that it can be used for
attribute validation. A TEI class can optionally override the
isValid method to implement tag-specific attribute
validation. A TagData instance is passed to the
isValid method at JSP translation time. Therefore if an
attribute allows runtime evaluation, the isValid method
can't perform a validation on it.
Let's step through an example that describes the TEI class, the handler, and the JSP that would use the tag. I will skip over the TLD file since it is just a matter of using the <teiclass>classname<teiclass> within the tag definition.
The scripting variables loopCounter,
loopCounter2, and loopCounter3 are defined
in the following TEI class to demonstrate the differences that scope
makes. It is possible that the information necessary to construct the
Variable info is passed in the tag attributes. In that case, the
values would be retrieved with the
data.getAttributeString method.
public class IterationTEI extends TagExtraInfo {
...
public VariableInfo[] getVariableInfo(TagData data) {
VariableInfo[] scriptVars = new VariableInfo[3];
// We are telling the constructor not to create
// a new variable since we will define it in our JSP
// loopCounter will be available in the body and
// remainder of the JSP
scriptVars[0] = new VariableInfo("loopCounter",
"java.lang.String",
false,
VariableInfo.AT_BEGIN);
// loopCounter2 will be available after the tag
//and for the remainder of the JSP
scriptVars[1] = new VariableInfo("loopCounter2",
"java.lang.String",
true,
VariableInfo.AT_END);
// loopCounter3 will be available in the tag only
scriptVars[1] = new VariableInfo("loopCounter3",
"java.lang.String",
true,
VariableInfo.NESTED);
return scriptVars;
}
}
Instead of defining the entire Java source for this class for the tag handler (we've already done this for other handlers), I will just include the pertinent methods for this example.
public class IterationTag extends BodySupport{
…
define any attribute setter methods
…
public int doStartTag() throws JspException{
// The TEI file defines loopCounter as
//AT_BEGIN and loopCounter3 as NESTED scope
// so we define them here. They can also be
// defined in doInitBody and modified/reset
// in doAfterBody
pageContext.setAttribute("loopCounter","0");
pageContext.setAttribute("loopCounter3","3");
return EVAL_BODY_TAG;
}
public int doEndTag() throws JspException{
…
do other processing
…
// The TEI file defines loopCounter2 as AT_END
// so we define it here
pageContext.setAttribute("loopCounter2","2");
return EVAL_BODY_TAG;
}
}
The JSP using these scripting variables would look like:
<%@ taglib uri="/oreillySample.tld" prefix="sample" %>
<%--
We told the TEI not to create this variable
so we are creating it here
--%>
<% String loopCounter; %>
<sample:iteration >
<%-- loopCounter is available starting in the body --%>
The value of loopCounter1 = <% loopCounter %><br />
<%-- loopCounter3 is only available in the body --%>
The value of loopCounter3 = <% loopCounter3 %><br />
</sample:iteration>
<%-- loopCounter is still available --%>
The value of loopCounter1 = <% loopCounter %><br />
<%-- loopCounter2 is available after the tag --%>
The value of loopCounter2 = <% loopCounter2 %><br />
Tags can also cooperate with each other by means of shared objects.
In the following example, tag1 creates a named object,
obj1, which is then reused by tag2. The
convention encouraged by the JSP specification is that a tag with an
attribute id creates and names an object, and the object is then
referenced by other tags with an attribute named
name.
<sample:tag1 id="obj1" attr1="value" />
<sample:tag2 name="obj1" />
This is also a useful feature when nesting tags since all objects created by the enclosing tag are available to all inner tags.
If you've gotten this far, then you should be ready to design and implement custom tag libraries, using all of their power in ways that work for your particular situation.
Sue Spielman is an associate editor for ONJava.com, covering JSP and Servlets technologies. She is also President and Senior Consulting Engineer for Switchback Software LLC.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.