Programming Simple Tag Handlers
The classes and interfaces used to implement simple tag handlers are contained in the
javax.servlet.jsp.tagextpackage. Simple tag handlers implement theSimpleTaginterface. Interfaces can be used to take an existing Java object and make it a tag handler. For most newly created handlers, you would use theSimpleTagSupportclasses as a base class.The heart of a simple tag handler is a single method--
doTag--which gets invoked when the end element of the tag is encountered. Note that the default implementation of thedoTagmethod ofSimpleTagSupportdoes nothing.A tag handler has access to an API that allows it to communicate with the JSP page. The entry point to the API is the JSP context object (
javax.servlet.jsp.JspContext). TheJspContextobject provides access to implicit objects.PageContextextendsJspContextwith servlet-specific behavior. Through these objects, a tag handler can retrieve all the other implicit objects (request, session, and application) that are accessible from a JSP page. If the tag is nested, a tag handler also has access to the handler (called the parent) that is associated with the enclosing tag.Including Tag Handlers in Web Applications
Tag handlers can be made available to a web application in two basic ways. The classes implementing the tag handlers can be stored in an unpacked form in the
WEB-INF/classes/subdirectory of the web application. Alternatively, if the library is distributed as a JAR, it is stored in theWEB-INF/lib/directory of the web application.How Is a Simple Tag Handler Invoked?
The
SimpleTaginterface defines the basic protocol between a simple tag handler and a JSP page's servlet. The JSP page's servlet invokes thesetJspContext,setParent, and attribute setting methods before callingdoStartTag.ATag t = new ATag(); t.setJSPContext(...); t.setParent(...); t.setAttribute1(value1); t.setAttribute2(value2); ... t.setJspBody(new JspFragment(...)) t.doTag();The following sections describe the methods that you need to develop for each type of tag introduced in Types of Tags.
Tag Handlers for Basic Tags
The handler for a basic tag without a body must implement the
doTagmethod of theSimpleTaginterface. ThedoTagmethod is invoked when the end element of the tag is encountered.The basic tag discussed in the first section,
<tt:basic />, would be implemented by the following tag handler:public HelloWorldSimpleTag extends SimpleTagSupport { public void doTag() throws JspException, IOException { getJspContext().getOut().write("Hello, world."); } }Tag Handlers for Tags with Attributes
Defining Attributes in a Tag Handler
For each tag attribute, you must define a set method in the tag handler that conforms to the JavaBeans architecture conventions. For example, consider the tag handler for the JSTL
c:iftag:This tag handler contains the following method:
Attribute Validation
The documentation for a tag library should describe valid values for tag attributes. When a JSP page is translated, a web container will enforce any constraints contained in the TLD element for each attribute.
The attributes passed to a tag can also be validated at translation time using the
validatemethod of a class derived fromTagExtraInfo. This class is also used to provide information about variables defined by the tag (see TagExtraInfo Class).The
validatemethod is passed the attribute information in aTagDataobject, which contains attribute-value tuples for each of the tag's attributes. Because the validation occurs at translation time, the value of an attribute that is computed at request time will be set toTagData.REQUEST_TIME_VALUE.The tag
<tt:twaattr1="value1"/>has the following TLDattributeelement:<attribute> <name>attr1</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute>This declaration indicates that the value of
attr1can be determined at runtime.The following
validatemethod checks whether the value ofattr1is a valid Boolean value. Note that because the value ofattr1can be computed at runtime,validatemust check whether the tag user has chosen to provide a runtime value.public class TwaTEI extends TagExtraInfo { public ValidationMessage[] validate(TagData data) { Object o = data.getAttribute("attr1"); if (o != null && o != TagData.REQUEST_TIME_VALUE) { if (((String)o).toLowerCase().equals("true") || ((String)o).toLowerCase().equals("false") ) return null; else return new ValidationMessage(data.getId(), "Invalid boolean value."); } else return null; } }Setting Dynamic Attributes
Simple tag handlers that support dynamic attributes must declare that they do so in the
tagelement of the TLD (see Declaring Tag Handlers). In addition, your tag handler must implement thesetDynamicAttributemethod of theDynamicAttributesinterface. For each attribute specified in the tag invocation that does not have a correspondingattributeelement in the TLD, the web container callssetDynamicAttribute, passing in the namespace of the attribute (ornullif in the default namespace), the name of the attribute, and the value of the attribute. You must implement thesetDynamicAttributemethod to remember the names and values of the dynamic attributes so that they can be used later whendoTagis executed. If thesetDynamicAttributemethod throws an exception, thedoTagmethod is not invoked for the tag, and the exception must be treated in the same manner as if it came from an attribute setter method.The following implementation of
setDynamicAttributesaves the attribute names and values in lists. Then, in thedoTagmethod, the names and values are echoed to the response in an HTML list.private ArrayList keys = new ArrayList(); private ArrayList values = new ArrayList(); public void setDynamicAttribute(String uri, String localName, Object value ) throws JspException { keys.add( localName ); values.add( value ); } public void doTag() throws JspException, IOException { JspWriter out = getJspContext().getOut(); for( int i = 0; i < keys.size(); i++ ) { String key = (String)keys.get( i ); Object value = values.get( i ); out.println( "<li>" + key + " = " + value + "</li>" ); } }Tag Handlers for Tags with Bodies
A simple tag handler for a tag with a body is implemented differently depending on whether or not the tag handler needs to manipulate the body. A tag handler manipulates the body when it reads or modifies the contents of the body.
Tag Handler Does Not Manipulate the Body
If a tag handler needs simply to evaluate the body, it gets the body using the
getJspBodymethod ofSimpleTagand then evaluates the body using theinvokemethod.The following tag handler accepts a
testparameter and evaluates the body of the tag if the test evaluates totrue. The body of the tag is encapsulated in a JSP fragment. If the test istrue, the handler retrieves the fragment using thegetJspBodymethod. Theinvokemethod directs all output to a supplied writer or, if the writer isnull, to theJspWriterreturned by thegetOutmethod of theJspContextassociated with the tag handler.public class IfSimpleTag extends SimpleTagSupport { private boolean test; public void setTest(boolean test) { this.test = test; } public void doTag() throws JspException, IOException { if(test){ getJspBody().invoke(null); } } }Tag Handler Manipulates the Body
If the tag handler needs to manipulate the body, the tag handler must capture the body in a
StringWriter. Theinvokemethod directs all output to a supplied writer. Then the modified body is written to theJspWriterreturned by thegetOutmethod of theJspContext. Thus, a tag that converts its body to uppercase could be written as follows:public class SimpleWriter extends SimpleTagSupport { public void doTag() throws JspException, IOException { StringWriter sw = new StringWriter(); jspBody.invoke(sw); jspContext(). getOut().println(sw.toString().toUpperCase()); } }Tag Handlers for Tags That Define Variables
Similar communication mechanisms exist for communication between JSP page and tag handlers as for JSP pages and tag files.
To emulate
INparameters, use tag attributes. A tag attribute is communicated between the calling page and the tag handler when the tag is invoked. No further communication occurs between the calling page and the tag handler.To emulate
OUTor nested parameters, use variables with availabilityAT_BEGIN,AT_END, orNESTED. The variable is not initialized by the calling page but instead is set by the tag handler.For
AT_BEGINavailability, the variable is available in the calling page from the start tag until the scope of any enclosing tag. If there's no enclosing tag, then the variable is available to the end of the page. ForAT_ENDavailability, the variable is available in the calling page after the end tag until the scope of any enclosing tag. If there's no enclosing tag, then the variable is available to the end of the page. For nested parameters, the variable is available in the calling page between the start tag and the end tag.When you develop a tag handler you are responsible for creating and setting the object referenced by the variable into a context that is accessible from the page. You do this by using the JspContext().setAttribute(name, value) or
JspContext.setAttribute(name,value,scope)method. You retrieve the page context using thegetJspContextmethod ofSimpleTag.Typically, an attribute passed to the custom tag specifies the name of the variable and the value of the variable is dependent on another attribute. For example, the
iteratortag introduced in Chapter 12 retrieves the name of the variable from thevarattribute and determines the value of the variable from a computation performed on thegroupattribute.public void doTag() throws JspException, IOException { if (iterator == null) return; while (iterator.hasNext()) { getJspContext().setAttribute(var, iterator.next()); getJspBody().invoke(null); } } public void setVar(String var) { this.var = var; } public void setGroup(Collection group) { this.group = group; if(group.size() > 0) iterator = group.iterator(); }The scope that a variable can have is summarized in Table 15-13. The scope constrains the accessibility and lifetime of the object.
TagExtraInfo Class
In Declaring Tag Variables for Tag Handlers we discussed how to provide information about tag variables in the tag library descriptor. Here we describe another approach: defining a tag extra info class. You define a tag extra info class by extending the class
javax.servlet.jsp.tagext.TagExtraInfo. ATagExtraInfomust implement thegetVariableInfomethod to return an array ofVariableInfoobjects containing the following information:The web container passes a parameter of type
javax.servlet.jsp.tagext.TagDatato thegetVariableInfomethod, which contains attribute-value tuples for each of the tag's attributes. These attributes can be used to provide theVariableInfoobject with an EL variable's name and class.The following example demonstrates how to provide information about the variable created by the
iteratortag in a tag extra info class. Because the name (var) and class (type) of the variable are passed in as tag attributes, they can be retrieved using thedata.getAttributeStringmethod and can be used to fill in theVariableInfoconstructor. To allow the variablevarto be used only within the tag body, you set the scope of the object toNESTED.package iterator; public class IteratorTEI extends TagExtraInfo { public VariableInfo[] getVariableInfo(TagData data) { String type = data.getAttributeString("type"); if (type == null) type = "java.lang.Object"; return new VariableInfo[] { new VariableInfo(data.getAttributeString("var"), type, true, VariableInfo.NESTED) }; } }The fully qualified name of the tag extra info class defined for an EL variable must be declared in the TLD in the
tei-classsubelement of thetagelement. Thus, thetei-classelement forIteratorTeiwould be as follows:Cooperating Tags
Tags cooperate by sharing objects. JSP technology supports two styles of object sharing.
The first style requires that a shared object be named and stored in the page context (one of the implicit objects accessible to JSP pages as well as tag handlers). To access objects created and named by another tag, a tag handler uses the
pageContext.getAttribute(name,scope)method.In the second style of object sharing, 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 by using the static method
SimpleTagSupport.findAncestorWithClass(from,class)or theSimpleTagSupport.getParentmethod. The former method should be used when a specific nesting of tag handlers cannot be guaranteed. After 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 privately named objects would have to be managed by the tag handler; one approach would be to use aMapto store name-object pairs.The following example illustrates a tag handler that supports both the named approach and the private object approach to sharing objects. In the example, the handler for a query tag checks whether an attribute named
connectionIdhas been set. If theconnectionIdattribute 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 SimpleTagSupport { public int doTag() 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 can 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="${requestScope.custNumber}" /> </tt:query> <tt:connection ... > <tt:query cid="balances"> SELECT account, balance FROM acct_table where customer_number = ? <tt:param value="${requestScope.custNumber}" /> </tt:query> </tt:connection>The TLD for the tag handler use the following declaration to indicate that the
connectionIdattribute is optional:Examples
The simple tags described in this section demonstrate solutions to two recurring problems in developing JSP applications: minimizing the amount of Java programming in JSP pages and ensuring a common look and feel across applications. In doing so, they illustrate many of the styles of tags discussed in the first part of the chapter.
An Iteration Tag
Constructing page content that is dependent on dynamically generated data often requires the use of flow control scripting statements. By moving the flow control logic to tag handlers, flow control tags reduce the amount of scripting needed in JSP pages. Iteration is a very common flow control function and is easily handled by a custom tag.
The discussion on using tag libraries in Chapter 12 introduced a tag library containing an
iteratortag. The tag retrieves objects from a collection stored in a JavaBeans component and assigns them to an EL variable. The body of the tag retrieves information from the variable. As long as elements remain in the collection, theiteratortag causes the body to be reevaluated. The tag in this example is simplified to make it easy to demonstrate how to program a custom tag. web applications requiring such functionality should use the JSTLforEachtag, which is discussed in Iterator Tags.JSP Page
The
index.jsppage invokes theiteratortag to iterate through a collection of department names. Each item in the collection is assigned to thedepartmentNamevariable.<%@ taglib uri="/tlt" prefix="tlt" %> <html> <head> <title>Departments</title> </head> <body bgcolor="white"> <jsp:useBean id="myorg" class="myorg.Organization"/> <table border=2 cellspacing=3 cellpadding=3> <tr> <td><b>Departments</b></td> </tr> <tlt:iterator var="departmentName" type="java.lang.String" group="${myorg.departmentNames}"> <tr> <td><a href="list.jsp?deptName=${departmentName}"> ${departmentName}</a></td> </tr> </tlt:iterator> </table> </body> </html>Tag Handler
The collection is set in the tag handler via the
groupattribute. The tag handler retrieves an element from the group and passes the element back to the page in the EL variable whose name is determined by thevarattribute. The variable is accessed in the calling page using the JSP expression language. After the variable is set, the tag body is evaluated with theinvokemethod.public void doTag() throws JspException, IOException { if (iterator == null) return; while (iterator.hasNext()) { getJspContext().setAttribute(var, iterator.next()); getJspBody().invoke(null); } } public void setVar(String var) { this.var = var; } public void setGroup(Collection group) { this.group = group; if(group.size() > 0) iterator = group.iterator(); }A Template Tag Library
A template provides a way to separate the common elements that are part of each screen from the elements that change with each screen of an application. Putting all the common elements together into one file makes it easier to maintain and enforce a consistent look and feel in all the screens. It also makes development of individual screens easier because the designer can focus on portions of a screen that are specific to that screen while the template takes care of the common portions.
The template is a JSP page that has placeholders for the parts that need to change with each screen. Each of these placeholders is referred to as a parameter of the template. For example, a simple template might include a title parameter for the top of the generated screen and a body parameter to refer to a JSP page for the custom content of the screen.
The template uses a set of nested tags--
definition,screen, andparameter--to define a table of screen definitions and uses aninserttag to insert parameters from a screen definition into a specific application screen.JSP Pages
The template for the Duke's Bookstore example,
template.jsp, is shown next. This page includes a JSP page that creates the screen definition and then uses theinserttag to insert parameters from the definition into the application screen.<%@ taglib uri="/tutorial-template" prefix="tt" %> <%@ page errorPage="/template/errorinclude.jsp" %> <%@ include file="/template/screendefinitions.jsp" %> <html> <head> <title> <tt:insert definition="bookstore" parameter="title"/> </title> </head> <body bgcolor="#FFFFFF"> <tt:insert definition="bookstore" parameter="banner"/> <tt:insert definition="bookstore" parameter="body"/> <center><em>Copyright © 2004 Sun Microsystems, Inc. </ em></center> </body> </html>The
screendefinitions.jsppage creates a definition for the screen specified by the request attribute javax.servlet.forward.servlet_path:<tt:definition name="bookstore" screen="${requestScope ['javax.servlet.forward.servlet_path']}"> <tt:screen id="/bookstore"> <tt:parameter name="title" value="Duke's Bookstore" direct="true"/> <tt:parameter name="banner" value="/template/banner.jsp" direct="false"/> <tt:parameter name="body" value="/bookstore.jsp" direct="false"/> </tt:screen> <tt:screen id="/bookcatalog"> <tt:parameter name="title" direct="true"> <jsp:attribute name="value" > <fmt:message key="TitleBookCatalog"/> </jsp:attribute> </tt:parameter> <tt:parameter name="banner" value="/template/banner.jsp" direct="false"/> <tt:parameter name="body" value="/bookcatalog.jsp" direct="false"/> </tt:screen> ... </tt:definition>The template is instantiated by the
Dispatcherservlet.Dispatcherfirst gets the requested screen.Dispatcherperforms business logic and updates model objects based on the requested screen. For example, if the requested screen is/bookcatalog,Dispatcherdetermines whether a book is being added to the cart based on the value of the Add request parameter. It sets the price of the book if it's on sale, and then adds the book to the cart. Finally, the servlet dispatches the request totemplate.jsp:public class Dispatcher extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { String bookId = null; BookDetails book = null; String clear = null; BookDBAO bookDBAO = (BookDBAO)getServletContext(). getAttribute("bookDBAO"); HttpSession session = request.getSession(); String selectedScreen = request.getServletPath(); ShoppingCart cart = (ShoppingCart)session. getAttribute("cart"); if (cart == null) { cart = new ShoppingCart(); session.setAttribute("cart", cart); } if (selectedScreen.equals("/bookcatalog")) { bookId = request.getParameter("Add"); if (!bookId.equals("")) { try { book = bookDBAO.getBookDetails(bookId); if ( book.getOnSale() ) { double sale = book.getPrice() * .85; Float salePrice = new Float(sale); book.setPrice(salePrice.floatValue()); } cart.add(bookId, book); } catch (BookNotFoundException ex) { // not possible } } } else if (selectedScreen.equals("/bookshowcart")) { bookId =request.getParameter("Remove"); if (bookId != null) { cart.remove(bookId); } clear = request.getParameter("Clear"); if (clear != null && clear.equals("clear")) { cart.clear(); } } else if (selectedScreen.equals("/bookreceipt")) { // Update the inventory try { bookDBAO.buyBooks(cart); } catch (OrderException ex) { request.setAttribute("selectedScreen", "/bookOrderError"); } } try { request. getRequestDispatcher( "/template/template.jsp"). forward(request, response); } catch(Exception ex) { ex.printStackTrace(); } } public void doPost(HttpServletRequest request, HttpServletResponse response) { request.setAttribute("selectedScreen", request.getServletPath()); try { request. getRequestDispatcher( "/template/template.jsp"). forward(request, response); } catch(Exception ex) { ex.printStackTrace(); } } }Tag Handlers
The template tag library contains four tag handlers--
DefinitionTag,ScreenTag,ParameterTag, andInsertTag--that demonstrate the use of cooperating tags.DefinitionTag,ScreenTag, andParameterTagconstitute a set of nested tag handlers that share private objects.DefinitionTagcreates a public object namedbookstorethat is used byInsertTag.In
doTag,DefinitionTagcreates a private object namedscreensthat contains a hash table of screen definitions. A screen definition consists of a screen identifier and a set of parameters associated with the screen. These parameters are loaded when the body of the definition tag, which contains nestedscreenandparametertags, is invoked.DefinitionTagcreates a public object of classDefinition, selects a screen definition from thescreensobject based on the URL passed in the request, and uses this screen definition to initialize a publicDefinitionobject.public int doTag() { try { screens = new HashMap(); getJspBody().invoke(null); Definition definition = new Definition(); PageContext context = (PageContext)getJspContext(); ArrayList params = (ArrayList) screens.get(screenId); Iterator ir = null; if (params != null) { ir = params.iterator(); while (ir.hasNext()) definition.setParam((Parameter)ir.next()); // put the definition in the page context context.setAttribute(definitionName, definition, context.APPLICATION_SCOPE); } }The table of screen definitions is filled in by
ScreenTagandParameterTagfrom text provided as attributes to these tags. Table 15-14 shows the contents of the screen definitions hash table for the Duke's Bookstore application.
If the URL passed in the request is /
bookstore, theDefinitionobject contains the items from the first row of Table 15-14 (see Table 15-15).
Duke's Bookstore/banner.jsp/bookstore.jsp
The parameters for the URL
/bookstoreare shown in Table 15-16. The parameters specify that the value of thetitleparameter,Duke's Bookstore, should be inserted directly into the output stream, but the values ofbannerandbodyshould be included dynamically.
titleDuke's Bookstoretruebanner/banner.jspfalsebody/bookstore.jspfalse
InsertTaginserts parameters of the screen definition into the response. ThedoTagmethod retrieves the definition object from the page context and then inserts the parameter value. If the parameter is direct, it is directly inserted into the response; otherwise, the request is sent to the parameter, and the response is dynamically included into the overall response.public void doTag() throws JspTagException { Definition definition = null; Parameter parameter = null; boolean directInclude = false; PageContext context = (PageContext)getJspContext(); // get the definition from the page context definition = (Definition)context.getAttribute( definitionName, context.APPLICATION_SCOPE); // get the parameter if (parameterName != null && definition != null) parameter = (Parameter) definition.getParam(parameterName); if (parameter != null) directInclude = parameter.isDirect(); try { // if parameter is direct, print to out if (directInclude && parameter != null) context.getOut().print(parameter.getValue()); // if parameter is indirect, include results of dispatching to page else { if ((parameter != null) && (parameter.getValue() != null)) context.include(parameter.getValue()); } } catch (Exception ex) { throw new JspTagException(ex.getMessage()); } }