SCWCD : Building a Custom Tag Library
Describe the semantics of the "Classic" custom tag event model when each event method
(doStartTag, doAfterBody, and
doEndTag) is executed, and explain what the return value
for each event method means; and write a tag handler class.
The classes and interfaces used to implement classic tag handlers are contained
in the javax.servlet.jsp.tagext package. Classic tag handlers
implement either the Tag, IterationTag, or
BodyTag interface. Interfaces can be used to take an existing
Java object and make it a tag handler. For newly created classic tag
handlers, you can use the
TagSupport and BodyTagSupport classes as
base classes. These classes and interfaces are contained in the
javax.servlet.jsp.tagext package.
Tag handler methods defined by the Tag and BodyTag
interfaces are called by the JSP page's servlet at various points during the
evaluation of the tag. When the start element of a custom tag is encountered, the JSP
page's servlet calls methods to initialize the appropriate handler and then invokes
the handler's doStartTag method. When the end element of a
custom tag is encountered, the handler's doEndTag method is
invoked for all but simple tags. Additional methods are invoked in between when a
tag handler needs to manipulate the body of the tag.
Table 10.1. Tag Handler Methods | Tag Type | Interface | Methods |
|---|
| Basic | Tag | doStartTag, doEndTag | | Attributes | Tag | doStartTag, doEndTag,
setAttribute1, ..., setAttributeN ,
release | | Body | Tag | doStartTag, doEndTag,
release | | Body, iterative evaluation | IterationTag | doStartTag,
doAfterBody,
doEndTag,
release | | Body, manipulation | BodyTag | doStartTag,
doInitBody,
doAfterBody,
doEndTag,
release |
A tag handler has access to an API that allows it to communicate with the
JSP page. The entry points to the API are two objects: the JSP context
(javax.servlet.jsp.JspContext) for simple tag handlers and
the page context (javax.servlet.jsp.PageContext) for classic
tag handlers. JspContext provides access to implicit objects.
PageContext extends JspContext with
HTTP-specific behavior. A tag handler can retrieve all the other implicit objects
(request, session, and
application) accessible from a JSP page through these objects.
In addition, implicit objects can have named attributes associated with them.
Such attributes are accessed using [set|get]Attribute methods.
The Tag interface defines the basic protocol between a tag
handler and a JSP page's servlet. It defines the life cycle and the methods to be
invoked when the start and end tags are encountered.
The JSP page's servlet invokes the setPageContext,
setParent, and attribute setting methods before calling
doStartTag. The JSP page's servlet also guarantees that
release will be invoked on the tag handler before the end of
the page.
Here is a typical tag handler method invocation sequence:
ATag t = new ATag();
t.setPageContext(...);
t.setParent(...);
t.setAttribute1(value1);
t.setAttribute2(value2);
t.doStartTag();
t.doEndTag();
t.release();
The BodyTag interface extends IterationTag by
defining additional methods that let a tag handler manipulate the content of evaluating
its body:
setBodyContent - Creates body content and adds to the
tag handler
doInitBody - Called before evaluation of the tag body.
This method will not be invoked for empty tags or for non-empty tags whose
doStartTag() method returns
SKIP_BODY or EVAL_BODY_INCLUDE.
A typical invocation sequence is:
t.doStartTag();
out = pageContext.pushBody();
t.setBodyContent(out);
// perform any initialization needed after body content is set
t.doInitBody();
t.doAfterBody();
// while IterationTag.doAfterBody() returns EVAL_BODY_AGAIN we
// iterate body evaluation
...
t.doAfterBody();
t.doEndTag();
out = pageContext.popBody();
t.release();
If the tag handler does not need to manipulate the body, the tag handler should implement
the Tag interface. If the tag handler implements the
Tag interface and the body of the tag needs to be evaluated,
the doStartTag method needs to return
Tag.EVAL_BODY_INCLUDE; otherwise it should return
Tag.SKIP_BODY.
If a tag handler needs to iteratively evaluate the body, it should implement the
IterationTag interface. The tag handler should return
IterationTag.EVAL_BODY_AGAIN from
IterationTag.doAfterBody
method if it determines that the body needs to be evaluated again.
If the tag handler needs to manipulate the body, the tag handler must implement
BodyTag (or be derived from BodyTagSupport).
When a tag handler implements the BodyTag interface, it must
implement the doInitBody and the
IterationTag.doAfterBody
methods. These methods manipulate body content passed to the tag handler by the JSP
page's servlet.
Body content supports several methods to read and write its contents. A tag handler
can use the body content's getString or getReader
methods to extract information from the body, and the writeOut(out)
method to write the body contents to an out stream. The writer
supplied to the writeOut method is obtained using the tag
handler's getPreviousOut method. This method is used to
ensure that a tag handler's results are available to an enclosing tag handler.
If the body of the tag needs to be evaluated, the doStartTag method
needs to return BodyTag.EVAL_BODY_BUFFERED; otherwise, it
should return Tag.SKIP_BODY.
The doInitBody method is called after the body content is
set but before it is evaluated. You generally use this method to perform any
initialization that depends on the body content.
The IterationTag.doAfterBody method is called AFTER the
body content is
evaluated. IterationTag.doAfterBody must return an
indication of whether to
continue evaluating the body. Thus, if the body should be evaluated again,
as would be the case if you were implementing an iteration tag,
IterationTag.doAfterBody should return
IterationTag.EVAL_BODY_AGAIN; otherwise,
IterationTag.doAfterBody should return
Tag.SKIP_BODY.
The following example reads the content of the body (which contains a SQL query)
and passes it to an object that executes the query. Since the body does not need
to be reevaluated, IterationTag.doAfterBody returns
Tag.SKIP_BODY:
public class QueryTag extends BodyTagSupport {
public int doAfterBody() throws JspTagException {
BodyContent bc = getBodyContent();
// get the bc as string
String query = bc.getString();
// clean up
bc.clearBody();
try {
Statement stmt = connection.createStatement();
result = stmt.executeQuery(query);
} catch (SQLException e) {
throw new JspTagException("QueryTag: " +
e.getMessage());
}
return SKIP_BODY;
}
}
When you extend TagSupport, the doStartTag
and doEndTag methods use the following return values
(defined as constants):
The following tag handler outputs messages from the doStartTag
and doEndTag methods. These messages form the contents of the tag
in the JSP page:
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.IOException;
public class SimpleTag extends TagSupport {
/**
* Executes when the tag is started.
*/
public int doStartTag() throws JspException {
try {
pageContext.getOut().print("Hello from doStartTag()");
// Allow text in the body of the tag.
return EVAL_BODY_INCLUDE;
} catch(IOException ioe) {
throw new JspException(ioe.getMessage());
}
}
/**
* Executes with the end tag.
*/
public int doEndTag() throws JspException {
try {
pageContext.getOut().print("Hello from doEndTag()");
// Continue evaluating the page.
return EVAL_PAGE;
} catch(IOException ioe) {
throw new JspException(ioe.getMessage());
}
}
}
If your custom tag must modify body content, extend the BodyTagSupport
class. It implements BodyTag. Provides the
doInitBody and IterationTag.doAfterBody methods.
Extend this class when your tag handler must modify body content. The
doStartTag method can return
BodyTag.EVAL_BODY_BUFFERED in addition to
Tag.EVAL_BODY_INCLUDE and Tag.SKIP_BODY.
The BodyContent object is a subclass of JspWriter.
JspWriter is the writer used internally for the JSP
out variable. The BodyContent object is
available to doInitBody,
IterationTag.doAfterBody, and
doEndTag through the bodyContent variable.
You can integrate this object's contents with the original JspWriter
in the doEndTag method. The BodyContent
object contains methods that you use to write output as well as methods to read,
clear, and retrieve its contents. For example, you can use
bodyContent.getString() to retrieve the writer's contents,
optionally modifying the contents before integrating them with the original
JspWriter.
The servlet container invokes the doInitBody method if the
doStartTag method returns
BodyTag.EVAL_BODY_BUFFERED. Use this method to initialize the
body content, if necessary. Then the tag handler processes the body, and invokes the
IterationTag.doAfterBody method.
The IterationTag.doAfterBody method returns
Tag.SKIP_BODY
or IterationTag.EVAL_BODY_AGAIN. If it returns
IterationTag.EVAL_BODY_AGAIN, the servlet container loops
back and re-executes the body. This lets you loop over repetitive data, such as
enumerations and database result sets.
The following example shows using the doInitBody and
IterationTag.doAfterBody methods.
It also shows how to integrate
bodyContent output with the output stream:
public class TestBody extends BodyTagSupport {
public int doStartTag() throws JspException {
try {
pageContext.getOut().print("doStartTag()");
return EVAL_BODY_BUFFERED;
} catch(IOException ioe) {
throw new JspException(ioe.getMessage());
}
}
public void doInitBody() throws JspException {
try {
// Note, that this is a different writer than the one
// you have in doStartTag and doEndTag.
bodyContent.print("doInitBody()");
} catch(IOException ioe) {
throw new JspException(ioe.getMessage());
}
}
public int doAfterBody() throws JspException {
try {
// Note, that this is a different writer than the one
// you have in doStartTag and doEndTag.
bodyContent.print("doAfterBody()");
// return IterationTag.EVAL_BODY_AGAIN;
// Use this to loop
return SKIP_BODY;
} catch(IOException ioe) {
throw new JspException(ioe.getMessage());
}
}
public int doEndTag() throws JspException {
try {
// Write from bodyContent writer to original writer.
pageContext.getOut().print(bodyContent.getString());
// Now we're back to the original writer.
pageContext.getOut().print("doEndTag()");
return EVAL_PAGE;
} catch(IOException ioe) {
throw new JspException(ioe.getMessage());
}
}
}
Using the PageContext API, write tag handler code to access the
JSP implicit variables and access web application attributes.
A PageContext is an object that provides a context to store
references to objects used by the page, encapsulates implementation-dependent
features, and provides convenience methods. A JSP page implementation class can
use a PageContext to run unmodified in any compliant JSP
container while taking
advantage of implementation-specific improvements like high performance
JspWriters.
The PageContext provides a number of facilities to the page/component author
and page implementor, including:
a single API to manage the various scoped namespaces
a number of convenience API's to access various public objects
a mechanism to obtain the JspWriter for output
a mechanism to manage session usage by the page
a mechanism to expose page directive attributes to the scripting environment
mechanisms to forward or include the current request to other active components
in the application
a mechanism to handle errorpage exception processing
public abstract class JspContext {
public abstract void setAttribute(String name, Object value);
public abstract void setAttribute(String name, Object value, int scope);
public abstract Object getAttribute(String name);
public abstract Object getAttribute(String name, int scope);
public abstract Object findAttribute(String name);
public abstract void removeAttribute(String name);
public abstract void removeAttribute(String name, int scope);
public abstract int getAttributesScope(String name);
public abstract Enumeration getAttributeNamesInScope(int scope);
public abstract JspWriter getOut();
}
public abstract class PageContext extends JspContext {
public abstract javax.servlet.http.HttpSession getSession();
public abstract java.lang.Object getPage();
public abstract javax.servlet.ServletRequest getRequest();
public abstract javax.servlet.ServletResponse getResponse();
public abstract java.lang.Exception getException();
public abstract javax.servlet.ServletConfig getServletConfig();
public abstract javax.servlet.ServletContext getServletContext();
public abstract void forward(java.lang.String relativeUrlPath)
throws javax.servlet.ServletException, java.io.IOException;
public abstract void include(java.lang.String relativeUrlPath)
throws javax.servlet.ServletException, java.io.IOException;
public abstract void handlePageException(java.lang.Exception e)
throws javax.servlet.ServletException, java.io.IOException;
}
Given a scenario, write tag handler code to access the parent tag and an arbitrary
tag ancestor.
An object created by the enclosing tag handler of a group of nested tags is available
to all inner tag handlers. This form of object sharing has the advantage that it
uses a private namespace for the objects, thus reducing the potential for naming conflicts.
To access an object created by an enclosing tag, a tag handler must first obtain its
enclosing tag with the static method
TagSupport.findAncestorWithClass(from, class) or the
TagSupport.getParent() method. The former method should be used
when a specific nesting of tag handlers cannot be guaranteed. Once the ancestor has
been retrieved, a tag handler can access any statically or dynamically created
objects. Statically created objects are members of the parent. Private objects can
also be created dynamically. Such objects can be stored in a tag handler with the
setValue method and retrieved with the getValue
method.
The following example illustrates a tag handler that supports both the
named and private object approaches to sharing objects. In the example,
the handler for a query tag checks whether an attribute named
connectionId has been
set. If the connection attribute has been set, the handler retrieves the connection
object from the page context. Otherwise, the tag handler first retrieves the tag
handler for the enclosing tag, and then retrieves the connection object from that
handler:
public class QueryTag extends BodyTagSupport {
public int doStartTag() throws JspException {
String cid = getConnectionId();
Connection connection;
if (cid != null) {
// there is a connection id, use it
connection =(Connection)pageContext.getAttribute(cid);
} else {
ConnectionTag ancestorTag =
(ConnectionTag) findAncestorWithClass(this,
ConnectionTag.class);
if (ancestorTag == null) {
throw new JspTagException("A query without
a connection attribute must be nested
within a connection tag.");
}
connection = ancestorTag.getConnection();
...
}
}
}
The query tag implemented by this tag handler could be used in either of
the following ways:
<tt:connection cid="con01" ... >
...
</tt:connection>
<tt:query id="balances" connectionId="con01">
SELECT account, balance FROM acct_table
WHERE customer_number = ?
<tt:param value="" />
</tt:query>
<tt:connection ... >
<tt:query cid="balances">
SELECT account, balance FROM acct_table
WHERE customer_number = ?
<tt:param value="" />
</tt:query>
</tt:connection>
The TLD for the tag handler indicates that the connectionId
attribute is optional with the following declaration:
<tag>
...
<attribute>
<name>connectionId</name>
<required>false</required>
</attribute>
</tag>
Describe the semantics of the "Simple" custom tag event model when the event method
(doTag) is executed; write a tag handler class; and explain the
constraints on the JSP content within the tag.
JSP 2.0 introduces a new type of tag extension called a Simple Tag Extension. Simple
Tag Extensions can be written in one of two ways:
In Java, by defining a class that implements the
javax.servlet.jsp.tagext.SimpleTag interface. This class is
intended for use by advanced page authors and tag library developers who need
the flexibility of the Java language in order to write their tag handlers. The
javax.servlet.jsp.tagext.SimpleTagSupport class provides a
default implementation for all methods in SimpleTag.
In JSP, using tag files. This method can be used by page authors who do not
know Java. It can also be used by advanced page authors or tag library developers
who know Java but are producing tag libraries that are presentation-centric
or can take advantage of existing tag libraries.
In addition to being simpler to work with, Simple Tag Extensions do not directly rely on
any servlet APIs, which allows for potential future integration with other technologies.
This is facilitated by the JspContext class, which
PageContext now extends. JspContext provides
generic services such as storing the JspWriter and keeping track of
scoped attributes, whereas PageContext has functionality specific to
serving JSPs in the context of servlets. Whereas the Tag interface
relies on PageContext, SimpleTag only relies
on JspContext. Unlike classic tag handlers,
SimpleTag does not extend Tag.
package javax.servlet.jsp.tagext;
public interface SimpleTag extends JspTag {
public void doTag()throws JspException, java.io.IOException;
public void setParent(JspTag parent);
public JspTag getParent();
public void setJspContext(JspContext pc);
public void setJspBody(JspFragment jspBody);
}
Most SimpleTag handlers should extend
javax.servlet.jsp.tagext.SimpleTagSupport. This is the
convenience class, similar to TagSupport or
BodyTagSupport. There are also some helpful methods included in this
class that include:
public JspFragment getJspBody() - returns the body passed
in by the container via setJspBody. The
JspFragment encapsulates the body of the tag. If the
JspFragment is null, it indicates that
tag has a body content type of empty.
public static final JspTag findAncestorWithClass(JspTag from,
java.lang.Class klass) - finds the instance of a given class type that
is closest to a given instance. This method uses the
getParent() method from the Tag and/or
SimpleTag interfaces. This method is used for coordination
among cooperating tags. While traversing the ancestors, for every instance
of TagAdapter (used to allow collaboration between
classic Tag handlers and SimpleTag
handlers) encountered, the tag handler returned by
TagAdapter.getAdaptee() is compared to
klass. In a case where the tag handler matches this
class, and not its TagAdapter, is returned.
The body of a Simple Tag, if present, is translated into a JSP Fragment and passed to the
setJspBody method. The tag can then execute the fragment as many
times as needed. Because JSP fragments do not support scriptlets, the
<body-content> of a SimpleTag cannot be
"JSP". A TLD is invalid if it specifies "JSP" as the value for
<body-content> for a tag whose handler implements the
SimpleTag interface. JSP containers are recommended to but not
required to produce an error if "JSP" is specified in this case.
During the translation phase, various pieces of the page are translated into implementations
of the javax.servlet.jsp.tagext.JspFragment abstract class, before
being passed to a tag handler.
JSP fragments are pieces of JSP code. Written using standard JSP syntax, a fragment is
translated into an implementation of the interface JspFragment for
use by tag handlers. A JSP fragment is used to represent the body of a tag for use with a
SimpleTag handler. The JspFragment
interface declares only two methods: invoke and
getJspContext.
package javax.servlet.jsp.tagext;
public abstract class JspFragment {
public abstract void invoke(java.io.Writer out)
throws JspException, java.io.IOException;
public abstract JspContext getJspContext();
}
invoke causes the fragment to be executed, writing the output
produced to the Writer passed to it. You can invoke a fragment
as many times as needed. invoke can throw a
SkipPageException, which signals that the fragment has determined
that the remainder of the page doesn't need to be evaluated.
The following example shows the simple custom tag:
public class MySimpleTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
getJspContext().getOut().write("Hello, World !");
}
}
The lifecycle of a Simple Tag Handler is straightforward and is not complicated by caching
semantics. Once a Simple Tag Handler is instantiated by the Container, it is executed and
then discarded. The same instance must not be cached and reused.
The following lifecycle events take place for the simple tag handler (in the same order):
A new tag handler instance is created each time the tag is encountered by
the container. This is done by calling the zero argument constructor on the
corresponding implementation class. It is important to note that a new instance
must be created for each tag invocation.
The setJspContext() and setParent()
methods are invoked on the tag handler. If the value being passed is
'null', then the setParent() method
need not be called. In the case of tag files, a
JspContext wrapper is created so that the tag
file can appear to have its own page scope. Calling
getJspContext() must return the wrapped
JspContext.
The container calls the setters for each attribute defined for this tag in
the order in which they appear in the JSP page or Tag File. If the attribute
value is an expression language expression or a runtime expression, it
gets evaluated first and is then passed on to the setter. On the other hand
if the attribute is a dynamic-attribute then
setDynamicAttribute() is called.
The setJspBody() method is called by the container
to set the body of this tag, as a JspFragment. A value of
null is passed to setJspBody() if the
tag is declared to have a <body-content> of
empty.
The doTag() method is called by the container. All tag
logic, iteration and body evaluations occur in this method.
All variables are synchronized after the doTag() method returns.
The following example simply writes the body to the output stream. When the
Writer given to invoke is null, the output from
invoke goes to the JspWriter associated with the
JspContext of the tag handler:
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;
public class MySimpleTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
getJspBody().invoke(null);
}
}
The invoke method directs all output to a supplied writer or to the
JspWriter returned by the getOut method of the
JspContext associated with the tag handler if the writer is
null.
<%@ taglib uri="/mytag" prefix="mytag" %>
<html>
<body>
<mytag:MySimpleTag>
Hello, World !
</mytag:MySimpleTag>
</body>
</html>
SimpleTagSupport provides the convenience method
getJspBody() to return the JspFragment generated for
the body content. The following example demonstrates that
a SimpleTag can obtain a copy of the body so that it can use or
MANIPULATE it:
public class MySimpleTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
StringWriter sw = new StringWriter();
getJspBody().invoke(sw);
getJspContex().getOut().write(sw.toString());
}
}
is equivalent to:
public class MySimpleTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
StringWriter sw = new StringWriter();
jspBody().invoke(sw);
getJspContex().getOut().write(sw.toString());
}
}
Describe the semantics of the Tag File model; describe the web application structure
for tag files; write a tag file; and explain the constraints on the JSP content in the
body of the tag.
Writing a traditional custom tag requires two steps:
Tag files simplify the process. First, tag files don't need to be compiled. They are
compiled as they are invoked. Also, tag files allow tag extensions to be written using only
JSP syntax. This means someone who does not know Java can also write tag extensions. Secondly,
a tag element in a tag library descriptor describes the name to be
used in a JSP page to reference the custom action. Using tag files, the name of a custom
action is the same as the tag file representing the action. Therefore, you don't need a
tag library descriptor at all. A tag file looks like a JSP page. It can have directives,
scripts, EL expressions, and standard and custom tags. A tag file has the
.tag or .tagx extension and can also include
other files that contain a common resource. An include file for a tag file has a
.tagf extension.
To work, tag files must be placed in the WEB-INF/tags directory under your
application directory OR a subdirectory under it. The container converts each tag file
found in (or uder) WEB-INF/tags into a tag handler.
A number of implicit objects are available from inside of a tag file. You can access
these objects from a script or an EL expression:
Table 10.2. The Tag Files implicit objects | Object | Type |
|---|
| request | javax.servlet.http.HttpServletRequest | | response | javax.servlet.http.HttpServletResponse | | out | javax.servlet.jsp.JspWriter | | session | javax.servlet.http.HttpSession | | application | javax.servlet.ServletContext | | config | javax.servlet.ServletConfig | | jspContext | javax.servlet.jsp.JspContext |
This is the example of a tag library in which the tag file simply writes a
String to the implicit out object:
<%— WEB-INF/tags/myExample.tld —%>
<%
out.println("Hello, World !");
%>
In JSP you need the taglib directive as usual, with the
prefix attribute to identify your tag library throughout the page.
NOTE, instead of a uri attribute, you have a tagdir
attribute. The tagdir attribute refers to the
WEB-INF/tags directory OR ANY subdirectory below
WEB-INF/tags:
<%@ taglib prefix="simpleTag" tagdir="/WEB-INF/tags" %>
<simpleTag:myExample />
Combined with the expression language (EL), you can really build a scriptless JSP
page very rapidly. The following example accepts an attribute from a calling JSP
page and converts it to the upper case:
<%— WEB-INF/tags/myExample2.tld —%>
<%@ attribute name="someAttribute" %>
<%
someAttribute = someAttribute.toUpperCase();
out.println(someAttribute);
%>
The following JSP page that uses the tag file:
<%@ taglib prefix="simpleTag" tagdir="/WEB-INF/tags" %>
<simpleTag:myExample2 someAttribute="hello" />
example, which is Java-code-free:
<%— WEB-INF/tags/myExample3.tag —%>
<%@ variable name-given="myVar" scope="AT_BEGIN" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="myVar" value="3"/>
After:
<jsp:doBody/>
To use the tag file, use the following JSP page:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sampleTag" tagdir="/WEB-INF/tags" %>
<c:set var="myVar" value="1"/>
Before: <br>
<simpleTag:myExample3/>
|