4 Implementing the Tag Handler
Simple Tag Handler Life Cycle (SimpleTag Interface)
Simple tag handlers present a much simpler invocation protocol than do classic tag handlers. The tag library descriptor maps tag library declarations to their physical underlying implementations. A simple tag handler is represented in Java by a class that implements the SimpleTag interface.
The life cycle 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. Initial performance metrics show that caching a tag handler instance does not necessarily lead to greater performance. To accommodate such caching makes writing portable tag handlers difficult and makes the tag handler prone to error.
In addition to being simpler to work with, simple tag extensions rely on JspContext rather than directly on the servlet-specific PageContext. PageContext extends JspContext. JspContext provides access to the JspWriter and scoped attributes, while PageContext adds facilities for JSP pages running in a servlet environment. The classic Tag interface uses PageContext; SimpleTag uses JspContext. If a SimpleTag has a body, the container passes it to setJspBody() as a JspFragment. The handler can invoke that fragment as many times as needed.
See JSP Fragments.
Unlike classic tag handlers, the SimpleTag interface does not extend Tag. Instead of supporting doStartTag() and doEndTag(), the SimpleTag interface provides a simple doTag() method, which is called once and only once for any given tag invocation. All tag logic, iteration, body evaluations, and so on are performed in this single method. Thus, simple tag handlers have the equivalent power of BodyTag, but with a much simpler life cycle and interface.
To support body content, the container calls setJspBody() with a JspFragment representing the tag body. The handler can call invoke() on that fragment to evaluate the body. SimpleTagSupport provides getJspBody() and other convenience methods.
Events in the Simple Tag Handler Life Cycle
The following describes the life cycle of simple tag handlers, from creation to invocation. When a simple tag handler is invoked, the following events occur in order:
-
Simple tag handlers are created initially using a zero argument constructor on the corresponding implementation class. Unlike classic tag handlers, a simple tag handler instance must never be pooled by the container. A new instance must be created for each tag invocation.
-
The container calls setJspContext() and, when the parent is non-null, setParent() on the tag handler. A tag file can receive a JspContext wrapper to provide its own page scope. In that case, getJspContext() returns the wrapped JspContext.
-
The attributes specified as XML element attributes (if any) are evaluated next, in the order in which they are declared, according to the following rules (referred to as "evaluating an XML element attribute"). The appropriate bean property setter is invoked for each. If no setter is defined for the specified attribute but the tag accepts dynamic attributes, the
setDynamicAttribute()method is invoked as the setter.If the attribute is a scripting expression (for example,
"<%= 1+1 %>"in JSP syntax, or"%= 1+1 %"in XML syntax), the expression is evaluated, and the result is converted and passed to the setter.Otherwise, if the attribute contains any expression language expressions (for example,
"Hello ${name}"), the expression is evaluated, and the result is converted and passed to the setter.Otherwise, the attribute value is taken verbatim, converted, and passed to the setter.
-
The container evaluates each <jsp:attribute> element in the order it appears and passes its value to the matching attribute setter. If the tag accepts dynamic attributes and no declared setter matches, the container calls setDynamicAttribute(). For an attribute whose declared type is JspFragment, the container creates a JspFragment and passes it to the setter. For other types, the container evaluates the <jsp:attribute> body, converts the result to the declared type, and passes that value to the setter.
-
If this invocation has a nonempty body, the container creates a JspFragment for it and calls setJspBody() on the handler. The body comes from <jsp:body>, or from the custom action body when there is no <jsp:body> or <jsp:attribute>. For tagdependent body content, the fragment supplies the body text verbatim; for scriptless content, it evaluates the body as scriptless JSP. The container does not call setJspBody() when the tag declares empty body content or this invocation has no body.
-
The
doTag()method is invoked. -
The implementation of
doTag()performs its function, potentially calling other tag handlers (if the tag handler is implemented as a tag file) and invoking fragments. -
The
doTag()method returns, and the tag handler instance is discarded. IfSkipPageExceptionis thrown, the rest of the page is not evaluated and the request is completed. If this request was forwarded or included from another page (or servlet), only the current page evaluation stops. -
For each tag scripting variable declared with scopes
AT_BEGINorAT_END, the appropriate scripting variables and scoped attributes are declared, as with classic tag handlers.
JSP Fragments
A JSP fragment is a portion of JSP code passed to a tag handler that can be invoked as many times as needed. You can think of a fragment as a template that is used by a tag handler to produce customized content. Thus, unlike simple attributes which are evaluated by the container, fragment attributes are evaluated by tag handlers during tag invocation.
JSP fragments can be parameterized using the JSP expression language (JSP EL) variables in the JSP code that composes the fragment. The JSP EL variables are set by the tag handler, thus allowing the handler to customize the fragment each time it is invoked.
See WebLogic JSP Reference in Developing Web Applications, Servlets, and JSPs for Oracle WebLogic Server.
During the simple tag handler life cycle, the body of a SimpleTag, if present, is translated into a JSP fragment and passed to the setJspBody method. During the translation phase, various sections of the page are translated into implementations of the jakarta.servlet.jsp.tagext.JspFragment abstract class, before being passed to a tag handler. This is done automatically for any JSP code in the body of a named attribute (one that is defined by <jsp:attribute>) that is declared to be a fragment, or of type JspFragment, in the tag library descriptor (TLD). This is also automatically done for the body of any tag handled by a simple tag handler. Once passed in, the tag handler can then evaluate and re-evaluate the fragment as many times as needed, or even pass it along to other tag handlers, in the case of tag files.
A JSP fragment can be parameterized by a tag handler by setting page-scoped attributes in the JspContext associated with the fragment. These attributes can then be accessed by way of the expression language.
A translation error must occur if a piece of JSP code that is to be translated into a JSP fragment contains scriptlets or scriptlet expressions.
Tag Handler Life Cycle (Tag and BodyTag Interfaces)
The methods inherited from the Tag or BodyTag interfaces and implemented by the tag handler class are invoked by the JSP engine at specific points during the processing of the JSP page. These methods signify points in the life cycle of a tag and are executed in the following sequence:
-
When the JSP container encounters a classic tag, it obtains a handler instance. The instance may be new or one that the container has reused from an earlier invocation. Before calling doStartTag(), the container calls setPageContext() and setParent() to provide the current page and enclosing tag. If the handler extends TagSupport or BodyTagSupport, those support classes provide the basic implementations of these methods.
-
The
setXXXX()JavaBean-like methods for each tag attribute are invoked. -
The JSP container calls doStartTag(). The handler returns one of these values:
- SKIP_BODY: Do not evaluate the body; proceed to doEndTag(). A tag declared with <body-content>empty</body-content> must return SKIP_BODY.
- EVAL_BODY_INCLUDE: Evaluate a nonempty body directly to the current JspWriter. A Tag, IterationTag, or BodyTag handler may return this value. For IterationTag and BodyTag, the container calls doAfterBody() after evaluating the body.
- BodyTag.EVAL_BODY_BUFFERED: For a BodyTag handler only, evaluate a nonempty body into a BodyContent buffer. The container first calls setBodyContent() and doInitBody().
Jakarta Pages 4.0 does not define BodyTag.EVAL_BODY_TAG. Do not use that constant in source code or documentation examples. If the action invocation has no body, the container proceeds to doEndTag() without the body callbacks even when doStartTag() returned an evaluation value.
-
Only on the EVAL_BODY_BUFFERED path for a nonempty action body, the container calls setBodyContent(BodyContent). The handler can retain that buffer to inspect or transform evaluated body text. BodyTagSupport already retains it and exposes getBodyContent(). The container does not call setBodyContent() for SKIP_BODY, EVAL_BODY_INCLUDE, or an empty action body.
-
Immediately after setBodyContent(), the container calls doInitBody() before the first buffered body evaluation. Use this callback for preparation that needs access to the active BodyContent. The container does not call doInitBody() on the EVAL_BODY_INCLUDE path.
-
After each nonempty body evaluation for an IterationTag or BodyTag handler, the container calls doAfterBody(). Return IterationTag.EVAL_BODY_AGAIN to evaluate the body again, or Tag.SKIP_BODY to stop iterating and proceed to doEndTag(). On the buffered path, the handler can inspect getBodyContent(); on the inclusive path, body output has already gone directly to the current JspWriter, so there is no BodyContent buffer for that evaluation.
-
On the buffered path, a handler that wants to emit its buffered result can write it to the enclosing writer obtained from BodyContent.getEnclosingWriter() or BodyTagSupport.getPreviousOut(). Do not write the entire accumulated buffer after every iteration or earlier iterations will appear repeatedly. On the inclusive path, no buffered-body copy is needed because the body output has already been written to the current writer.
-
The
doEndTag()method is invoked. Your tag handler can implement this method to perform post-tag, server side work, write output to the parent scopeJspWriter, or close resources such as database connections.Your tag handler writes output directly to the surrounding scope using the JspWriter obtained from pageContext.getOut() in doEndTag(). On the buffered path, the JSP container restores the enclosing writer before calling doEndTag(). Do not manipulate the page-context writer stack directly in the handler.
You can control the flow for evaluation of the rest of the JSP page by returning one of the following values from the
doEndTag()method:EVAL_PAGE—Directs the JSP engine to continue processing the rest of the JSP page.SKIP_PAGE—Directs the JSP engine to skip the rest of the JSP page. -
After all invocations on this classic tag handler instance have finished, the container calls release() before discarding the instance. release() is for long-term state and is not called after every doEndTag(). A classic handler may have handled several invocations before this callback.
Iteration Over a Body Tag (IterationTag Interface)
A tag that implements the jakarta.servlet.jsp.tagext.IterationTag interface has a method available called doAfterBody() that allows you to conditionally re-evaluate the body of the tag. If doAfterBody() returns IterationTag.EVAL_BODY_AGAIN the body is re-evaluated, if doAfterBody() returns Tag.SKIP_BODY, the body is skipped and the doEndTag() method is called.
For the complete contract, see the Jakarta Pages 4.0 IterationTag API at https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/tagext/IterationTag.html.
Handling Exceptions within a Tag Body
You can catch exceptions thrown from within a tag by implementing the doCatch() and doFinally() methods of the jakarta.servlet.jsp.tagext.TryCatchFinally interface.
For the complete contract, see the Jakarta Pages 4.0 TryCatchFinally API at https://jakarta.ee/specifications/pages/4.0/apidocs/jakarta.servlet.jsp/jakarta/servlet/jsp/tagext/TryCatchFinally.html.
Using Tag Attributes
Your custom tags can define any number of attributes that can be specified from the JSP page. You can use these attributes to pass information to the tag handler and customize its behavior.
You declare each attribute name in the TLD in the <attribute> element. This declares the name of the attribute and other attribute properties.
For each writable tag attribute declared in the TLD, the handler provides a public setter whose name and parameter type match that attribute. For an attribute named foo of type String, the handler can declare:
import jakarta.servlet.jsp.tagext.TagSupport;
public class ExampleTag extends TagSupport {
private String foo;
public void setFoo(String value) {
this.foo = value;
}
}The first letter of the attribute name is capitalized after set. A matching getter is optional; the JSP container does not require one merely to set the attribute. The handler can store the supplied value in a field for its other methods to use.
The JSP engine invokes the setter methods for each attribute appropriately after the tag handler is initialized and before the doStartTag() method is called. Generally, you should implement the setter methods to store the attribute value in a member variable that is accessible to the other methods of the tag handler.
Defining New Scripting Variables
Your tag handler can introduce new scripting variables that can be referenced by the JSP page at various scopes. Scripting variables can be used like implicit objects within their defined scope.
Define a new scripting variable by using the <tei-class> element to identify a Java class that extends jakarta.servlet.jsp.tagext.TagExtraInfo. For example:
<tei-class>examples.webapp.jsp.tags.ext.session.ListTagExtraInfo</tei-class>
Then write the TagExtraInfo class.
For example:
package examples.webapp.jsp.tags.ext.session;
import jakarta.servlet.jsp.tagext.TagData;
import jakarta.servlet.jsp.tagext.TagExtraInfo;
import jakarta.servlet.jsp.tagext.VariableInfo;
public class ListTagExtraInfo extends TagExtraInfo {
@Override
public VariableInfo[] getVariableInfo(TagData data) {
return new VariableInfo[] {
new VariableInfo("name", "java.lang.String", true, VariableInfo.NESTED),
new VariableInfo("value", "java.lang.String", true, VariableInfo.NESTED)
};
}
}This class declares two nested scripting variables, name and value, both of type java.lang.String. The JSP container makes them available while evaluating the body of the list tag. The tag handler sets page-scoped attributes with those exact names before each body evaluation.
The constructor for VariableInfo() takes four arguments.
-
A
Stringthat defines the name of the new variable. -
A
Stringthat defines the Java type of the variable. Give the full package name for types in packages other than thejava.langpackage. - A boolean indicating whether this is a new scripting variable to declare. In this example, both values are true because both variables are new.
-
An
intdeclaring the scope of the variable. Use a static field fromVariableInfoshown here:VariableInfo.NESTED—Available only within the start and end tags of the tag.VariableInfo.AT_BEGIN—Available from the start tag until the end of the page.VariableInfo.AT_END—Available from the end tag until the end of the page.
Configure your tag handler to initialize the value of the scripting variables via the page context. For example, the following Java source could be used in the doStartTag() method to initialize the values of the scripting variables defined above:
pageContext.setAttribute("name", name);
pageContext.setAttribute("value", value);In the release-branch ListTag.doStartTag() example, the local variables name and value are Strings read from the session. The page-context attribute names must match the VariableInfo names exactly. The same two assignments are made before each subsequent body evaluation in ListTag.doAfterBody().
You can also access variables created with the TagExtraInfo class by referencing it the same way you access a JavaBean that was created with useBean.
Dynamically Named Scripting Variables
You can define the name of a new scripting variable from a tag attribute. This definition enables you to use multiple instances of a tag that define a scripting variable at the same scope, without the scripting variables of the tag clashing. If you want to achieve this from your class that extends TagExtraInfo, you must get the name of the scripting variable from the TagData that is passed into the getVariableInfo() method.
From TagData, you can retrieve the value of the attribute that names the scripting variable using the getAttributeString() method. There is also the getId() method that returns the value of the id attribute, which is often used to name a new implicit object from JSP tag.
Defining Variables in the Tag Library Descriptor
You can define variables in the TLD.
For more information, see Define scripting variables.
Writing Cooperative Nested Tags
You can design your tags to implicitly use properties from tags they are nested within. For example, in the code example called SQL Query (see the samples/examples/jsp/tagext/sql directory of your WebLogic Server installation) a <sql:query> tag is nested within a <sql:connection> tag. The query tag searches for a parent scope connection tag and uses the JDBC connection established by the parent scope.
To locate a parent scope tag, your nested tag uses the static findAncestorWithClass() method of the TagSupport class. The following is an example taken from the QueryTag example.
ConnectionTag connectionTag = (ConnectionTag)
jakarta.servlet.jsp.tagext.TagSupport.findAncestorWithClass(
this, ConnectionTag.class);
if (connectionTag == null) {
throw new JspException("Query tag must be nested in a connection tag");
}
conn = connectionTag.getConnection();findAncestorWithClass(this, ConnectionTag.class) searches outward through enclosing tag handlers and returns the nearest ConnectionTag instance. It returns null if no such ancestor exists. The null check above turns that condition into a JspException before the handler calls getConnection(). This lookup does not throw ClassNotFoundException because the class is supplied directly as ConnectionTag.class.
Using this feature in your custom tags can simplify the syntax and usage of tags in the JSP page.
Using a Tag Library Validator
A tag library validator is a Java class that extends jakarta.servlet.jsp.tagext.TagLibraryValidator and checks the XML view of a JSP page during translation. The JSP container calls validate(String prefix, String uri, PageData page). That method returns a ValidationMessage[] array containing any validation errors. It returns null or an empty array when the page is valid. The PageData argument supplies the page's XML view; the method does not return a String.
To implement a Tag Library Validator: