Custom Tags in JSP Pages

 


Overview

The standard JSP tags for invoking operations on JavaBeans components and performing request dispatching simplify JSP page development and maintenance. JSP technology also provides a mechanism for encapsulating other types of dynamic functionality in custom tags, which are extensions to the JSP language. Custom tags are usually distributed in the form of a tag library, which defines a set of related custom tags and contains the objects that implement the tags.

Some examples of tasks that can be performed by custom tags include operations on implicit objects, processing forms, accessing databases and other enterprise services such as e-mail and directories, and performing flow control. JSP tag libraries are created by developers who are proficient at the Java programming language and expert in accessing data and other services, and are used by Web app designers who can focus on presentation issues rather than being concerned with how to access enterprise services. As well as encouraging division of labor between library developers and library users, custom tags increase productivity by encapsulating recurring tasks so that they can be reused across more than one app.

Tag libraries are receiving a great deal of attention in the JSP technology community. For more information about tag libraries and for pointers to some freely-available libraries see

http://java.sun.com/products/jsp/taglibraries.html

 


What Is a Custom Tag?

A custom tag is a user-defined JSP language element. When a JSP page containing a custom tag is translated into a servlet, the tag is converted to operations on an object called a tag handler. The Web container then invokes those operations when the JSP page's servlet is executed.

Custom tags have a rich set of features. They can

  • Be customized via attributes passed from the calling page.
  • Access all the objects available to JSP pages.
  • Modify the response generated by the calling page.
  • Communicate with each other. You can create and initialize a JavaBeans component, create a variable that refers to that bean in one tag, and then use the bean in another tag.
  • Be nested within one another, allowing for complex interactions within a JSP page.

 


The Example JSP Pages

This chapter describes the tasks involved in using and defining tags. The chapter illustrates the tasks with excerpts from the JSP version of the Duke's Bookstore app discussed in Chapter 11 rewritten to take advantage of two tag libraries: Struts and tutorial-template. The section in this chapter entitled Examples describes some tags in detail: the iterate tag from Struts and the set of tags in the tutorial-template tag library.

The Struts tag library provides a framework for building internationalized Web apps that implement the Model-View-Controller design pattern. Struts includes a comprehensive set of utility custom tags for handling

  • HTML forms
  • Templates
  • JavaBeans components
  • Logic processing

The Duke's Bookstore app uses tags from the Struts bean and logic sublibraries.

The tutorial-template tag library defines a set of tags for creating an app template. The template is a JSP page with 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 could include a title parameter and a body parameter that refers to a JSP page for the custom content of the screen. The template is created with a set of nested tags--definition, screen, and parameter--that are used to build a table of screen definitions for Duke's Bookstore and with an insert tag to insert parameters from the table into the screen.

Figure 13-1 shows the flow of a request through the following Duke's Bookstore Web components:

  • template.jsp, which determines the structure of each screen. It uses the insert tag to compose a screen from subcomponents.
  • screendefinitions.jsp, which defines the subcomponents used by each screen. All screens have the same banner, but different title and body content (specified by the JSP Pages column in Table 11-1).
  • Dispatcher, a servlet, which processes requests and forwards to template.jsp.

Figure 13-1 Request Flow through Duke's Bookstore Components

The source for the Duke's Bookstore app is located in the j2eetutorial/examples/src/web/bookstore3 directory. To build, deploy, and run the example:

  1. Go to j2eetutorial/examples and build the app by executing ant bookstore3

  2. Download and unpack Struts version 1.0 from
       http://jakarta.apache.org/builds/jakarta-struts/
          release/v1.0/
    
    
    Copy struts-bean.tld, struts-logic.tld, and struts.jar from jakarta-struts-1.0/lib to examples/build/web/bookstore3
  3. Start the j2ee server.
  4. Start deploytool.
  5. Start the Cloudscape database by executing cloudscape -start.
  6. If you have not already created the bookstore database, run ant create-web-db.
  7. Create a J2EE app called Bookstore3App.
    1. Select FileNew Application.
    2. In the file chooser, navigate to j2eetutorial/examples/src/web/bookstore3.
    3. In the File Name field, enter Bookstore3App.
    4. Click New Application.
    5. Click OK.
  8. Create the WAR and add the DispatcherServlet Web component and all of the Duke's Bookstore content to Bookstore3App.
    1. Select FileNew --> Web Component.
    2. Click the Create New WAR File in Application radio button and select Bookstore3App from the combo box. Enter Bookstore3WAR in the field labeled WAR Display Name.
    3. Click Edit to add the content files. In the Edit Contents dialog box, navigate to j2eetutorial/examples/build/web/bookstore3. Select Dispatcher.class and click Add. Add the JSP pages banner.jsp, bookstore.jsp, bookdetails.jsp, catalog.jsp, showcart.jsp, cashier.jsp, receipt.jsp, initdestroy.jsp, template.jsp, screendefinitions.jsp, and errorpage.jsp. Add duke.books.gif, struts-bean.tld, struts-logic.tld, tutorial-template.tld, and struts.jar. Add the cart, database, messages, taglib, and util packages. Click OK.
    4. Click Next.
    5. Select the Servlet radio button.
    6. Click Next.
    7. Select Dispatcher from the Servlet Class combo box.
    8. Click Next twice.
    9. In the Component Aliases pane, click Add and then type /enter in the Alias field. Repeat to add the aliases /catalog, /bookdetails, /showcart, /cashier, and /receipt.
    10. Click Finish.
  9. Add the BookDBEJB enterprise bean that you created in the section The Example JSP Pages.
    1. Select FileAdd --> EJB JAR.
    2. Navigate to the directory examples/build/web/ejb.
    3. Select bookDB.jar.
    4. Click Add EJB JAR.
  10. Add a reference to the enterprise bean BookDBEJB.
    1. Select Bookstore3WAR.
    2. Select the EJB Refs tab.
    3. Click Add.
    4. Enter ejb/BookDBEJB in the Coded Name column.
    5. Enter Session in the Type column.
    6. Select Remote in the Interfaces column.
    7. Enter database.BookDBEJBHome in the Home Interface column.
    8. Enter database.BookDBEJB in the Local/Remote Interface column.
  11. Add the tag library URI to location mappings (see Declaring Tag Libraries):
    1. Select the File Refs tab.
    2. Click the Add button in the JSP Tag Libraries subpane.
    3. Enter the relative URI /tutorial-template in the Coded Reference field.
    4. Enter the absolute location /WEB-INF/tutorial-template.tld in the Tag Library field.
    5. Repeat for /struts-bean to /WEB-INF/struts-bean.tld and /struts-logic to /WEB-INF/struts-logic.tld.
  12. Specify the JNDI names.
    1. Select Bookstore3App.
    2. In the Application table, locate the EJB component and enter BookDBEJB in the JNDI Name column.
    3. In the References table, locate the EJB Ref and enter BookDBEJB in the JNDI Name column.
    4. In the References table, locate the Resource component and enter jdbc/Cloudscape in the JNDI Name column.
  13. Enter the context root.
    1. Select the Web Context tab.
    2. Enter bookstore3.
  14. Deploy the app.
    1. Select ToolsDeploy.
    2. Click Finish.
  15. Open the bookstore URL http://<host>:8000/bookstore3/enter.

 


Using Tags

This section describes how a page author specifies that a JSP page is using a tag library and introduces the different types of tags.

 

Declaring Tag Libraries

You declare that a JSP page will use tags defined in a tag library by including a taglib directive in the page before any custom tag is used:

<%@ taglib uri="/WEB-INF/tutorial-template.tld" prefix="tt" %>

The uri attribute refers to a URI that uniquely identifies the tag library descriptor (TLD), described in the section Tag Library Descriptors. This URI can be direct or indirect. The prefix attribute defines the prefix that distinguishes tags defined by a given tag library from those provided by other tag libraries.

Tag library descriptor file names must have the extension .tld. TLD files are stored in the WEB-INF directory of the WAR or in a subdirectory of WEB-INF. You can reference a TLD directly and indirectly.

The following taglib directive directly references a TLD filename:

<%@ taglib uri="/WEB-INF/tutorial-template.tld" prefix="tt" %>

This taglib directive uses a short logical name to indirectly reference the TLD:

<%@ taglib uri="/tutorial-template" prefix="tt" %>

A logical name must be mapped to an absolute location in the Web app deployment descriptor. To map the logical name /tutorial-template to the absolute location /WEB-INF/tutorial-template.tld:

  1. Select Bookstore3WAR.
  2. Select the File Refs tab.
  3. Click the Add button in the JSP Tag Libraries subpane.
  4. Enter the relative URI /tutorial-template in the Coded Reference field.
  5. Enter the absolute location /WEB-INF/tutorial-template.tld in the Tag Library field.

 

Types of Tags

JSP custom tags are written using XML syntax. They have a start tag and end tag, and possibly a body:

<tt:tag>
   body
</tt:tag>

A custom tag with no body is expressed as follows:

<tt:tag />

Simple Tags

A simple tag contains no body and no attributes:

<tt:simple />

Tags With Attributes

A custom tag can have attributes. Attributes are listed in the start tag and have the syntax attr="value". Attribute values serve to customize the behavior of a custom tag just as parameters are used to customize the behavior of a method.

You specify the types of a tag's attributes in a tag library descriptor (see Tag Library Descriptors).

You can set an attribute value from a String constant or a runtime expression. The conversion process between the constants and runtime expressions and attribute types follows the rules described for JavaBeans component properties in Setting JavaBeans Component Properties.

The attributes of the Struts logic:present tag determine whether the body of the tag is evaluated. In the following example, an attribute specifies a request parameter named Clear:

<logic:present parameter="Clear">

The Duke's Bookstore app page catalog.jsp uses a runtime expression to set the value of the attribute that determines the collection of books over which the Struts logic:iterate tag iterates:

<logic:iterate collection="<%=bookDB.getBooks()%>" 
   id="book" type="database.BookDetails">

Tags with Bodies

A custom tag can contain custom and core tags, scripting elements, HTML text, and tag-dependent body content between the start and end tag.

In the following example, the Duke's Bookstore app page showcart.jsp uses the Struts logic:present tag to clear the shopping cart and print a message if the request contains a parameter named Clear:

<logic:present parameter="Clear">
   <% cart.clear(); %>
   <font size="+2"><strong> 
   You just cleared your shopping cart! 
   </strong><br>&nbsp;<br></font>
</logic:present>

Choosing between Passing Information as Attributes or Body

As shown in the last two sections, it is possible to pass a given piece of data as an attribute of the tag or as the tag's body. Generally speaking, any data that is a simple string or can be generated by evaluating a simple expression is best passed as an attribute.

Tags That Define Scripting Variables

A custom tag can define a variable that can be used in scripts within a page. The following example illustrates how to define and use a scripting variable that contains an object returned from a JNDI lookup. Examples of such objects include enterprise beans, transactions, databases, environment entries, and so on:

<tt:lookup id="tx" type="UserTransaction" 
   name="java:comp/UserTransaction" />
<% tx.begin(); %>

In the Duke's Bookstore app, several pages use bean-oriented tags from Struts to define scripting variables. For example, bookdetails.jsp uses the bean:parameter tag to create the bookId scripting variable and set it to the value of the bookId request parameter. The jsp:setProperty statement also sets the bookId property of the bookDB object to the value of the bookId request parameter. The bean:define tag retrieves the value of the bookstore database property bookDetails and defines the result as the scripting variable book:

<bean:parameter id="bookId" name="bookId" />
<jsp:setProperty name="bookDB" property="bookId"/>
<bean:define id="book" name="bookDB" property="bookDetails"
   type="database.BookDetails"/>
<h2><jsp:getProperty name="book" property="title"></h2>

Cooperating Tags

Customer tags can cooperate with each other through shared objects. In the following example, tag1 creates an object called obj1, which is then reused by tag2.

<tt:tag1 attr1="obj1" value1="value" />
<tt:tag2 attr1="obj1" />

In the next example, an object created by the enclosing tag of a group of nested tags is available to all inner tags. Since the object is not named, the potential for naming conflicts is reduced. This example illustrates how a set of cooperating nested tags would appear in a JSP page.

<tt:outerTag>
   <tt:innerTag />
</tt:outerTag>

The Duke's Bookstore page template.jsp uses a set of cooperating tags to define the screens of the app. These tags are described in the section A Template Tag Library.

 


Defining Tags

To define a tag, you need to:

  • Develop a tag handler and helper classes for the tag
  • Declare the tag in a tag library descriptor

This section describes the properties of tag handlers and TLDs and explains how to develop tag handlers and library descriptor elements for each type of tag introduced in the previous section.

 

Tag Handlers

A tag handler is an object invoked by a Web container to evaluate a custom tag during the execution of the JSP page that references the tag. Tag handlers must implement either the Tag or BodyTag interface. Interfaces can be used to take an existing Java object and make it a tag handler. For newly created 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 tag 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 tag of a custom tag is encountered, the handler's doEndTag method is invoked. Additional methods are invoked in between when a tag handler needs to interact with the body of the tag. For further information, see How Is a Tag Handler Invoked?. In order to provide a tag handler implementation, implement the methods, summarized in Table 13-1, that are invoked at various stages of processing the tag.

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 page context object (javax.servlet.jsp.PageContext), through which a tag handler can retrieve all the other implicit objects (request, session, and app) accessible from a JSP page.

Implicit objects can have named attributes associated with them. Such attributes are accessed using [set|get]Attribute methods.

If the tag is nested, a tag handler also has access to the handler (called the parent) associated with the enclosing tag.

 

Table 13-1 Tag Handler Methods 
Tag Handler Type
Methods
Simple doStartTag, doEndTag, release
Attributes doStartTag, doEndTag, set/getAttribute1...N, release
Body, evaluation and no interaction doStartTag, doEndTag, release
Body, iterative evaluation doStartTag, doAfterBody, doEndTag, release
Body, interaction doStartTag, doEndTag, release, doInitBody, doAfterBody, release

A set of related tag handler classes (a tag library) is usually packaged and deployed as a JAR archive.

 

Tag Library Descriptors

A tag library descriptor (TLD) is an XML document that describes a tag library. A TLD contains information about a library as a whole and about each tag contained in the library. TLDs are used by a Web container to validate the tags and by JSP page development tools.

TLD file names must have the extension .tld. TLD files are stored in the WEB-INF directory of the WAR file or in a subdirectory of WEB-INF. When you add a TLD to a WAR using deploytool, it automatically puts it into WEB-INF.

A TLD must begin with an XML document prolog that specifies the version of XML and the document type definition (DTD):

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag 
Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">

The J2EE SDK version 1.3 can understand version 1.1 and 1.2 DTDs. However, this chapter documents the 1.2 version because you should use the newer version in any tag libraries that you develop. The template library TLD, tutorial-template.tld, conforms to the 1.2 version. The Struts library TLDs conform to the 1.1 version of the DTD, which has fewer elements and uses slightly different names for some of the elements.

The root of a TLD is the taglib element. The subelements of taglib are listed in Table 13-2:

 

Table 13-2 taglib Subelements 
Element
Description
tlib-version The tag library's version
jsp-version The JSP specification version that the tag library requires
short-name Optional name that could be used by a JSP page authoring tool to create names with a mnemonic value
uri A URI that uniquely identifies the tag library
display-name Optional name intended to be displayed by tools
small-icon Optional small icon that can be used by tools
large-icon Optional large icon that can be used by tools
description Optional tag-specific information
listener
tag See tag Element

listener Element

A tag library can specify some classes that are event listeners. The listeners are listed in the TLD as listener elements, and the Web container will instantiate the listener classes and register them in a way analogous to listeners defined at the WAR level. Unlike WAR-level listeners, the order in which the tag library listeners are registered is undefined. The only subelement of the listener element is the listener-class element, which must contain the fully qualified name of the listener class.

tag Element

Each tag in the library is described by giving its name and the class of its tag handler, information on the scripting variables created by the tag, and information on the tag's attributes. Scripting variable information can be given directly in the TLD or through a tag extra info class (see Tags That Define Scripting Variables). Each attribute declaration contains an indication of whether the attribute is required, whether its value can be determined by request-time expressions, and the type of the attribute (see Tags with Attributes).

A tag is specified in a TLD in a tag element. The subelements of tag are listed in Table 13-3:

 

Table 13-3 tag Subelements 
Element
Description
name The unique tag name.
tag-class The fully-qualified name of the tag handler class.
tei-class Optional subclass of javax.servlet.jsp.tagext.TagExtraInfo. See TagExtraInfo Class.
body-content The body content type. See body-content Element and body-content Element.
display-name Optional name intended to be displayed by tools.
small-icon Optional small-icon that can be used by tools.
large-icon Optional large-icon that can be used by tools.
description Optional tag-specific information.
variable Optional scripting variable information. See variable Element.
attribute Tag attribute information. See attribute Element.

The following sections describe the methods and TLD elements that you need to develop for each type of tag introduced in Using Tags.

 

Simple Tags

Tag Handlers

The handler for a simple tag must implement the doStartTag and doEndTag methods of the Tag interface. The doStartTag method is invoked when the start tag is encountered. This method returns SKIP_BODY because a simple tag has no body. The doEndTag method is invoked when the end tag is encountered. The doEndTag method needs to return EVAL_PAGE if the rest of the page needs to be evaluated; otherwise, it should return SKIP_PAGE.

The simple tag discussed in the first section,

<tt:simple />

would be implemented by the following tag handler:

public SimpleTag extends TagSupport {
   public int doStartTag() throws JspException {
      try {
         pageContext.getOut().print("Hello.");
      } catch (Exception ex) {
         throw new JspTagException("SimpleTag: " + 
            ex.getMessage());
      }
      return SKIP_BODY;
   }
   public int doEndTag() {
      return EVAL_PAGE;
   }
}

body-content Element

Tags without bodies must declare that their body content is empty using the body-content element:

<body-content>empty</body-content>

 

Tags with Attributes

Defining Attributes in a Tag Handler

For each tag attribute, define a property and get and set methods that conform to the JavaBeans architecture conventions in the tag handler. For example, the tag handler for the Struts logic:present tag,

<logic:present parameter="Clear">

contains the following declaration and methods:

protected String parameter = null;
public String getParameter() {
   return (this.parameter);
}
public void setParameter(String parameter) {
   this.parameter = parameter;
}

Note that if your attribute is named id and your tag handler inherits from the TagSupport class, you do not need to define the property and set and get methods because these are already defined by TagSupport.

A tag attribute whose value is a String can name an attribute of one of the implicit objects available to tag handlers. An implicit object attribute would be accessed by passing the tag attribute value to the [set|get]Attribute method of the implicit object. This is a good way to pass scripting variable names to a tag handler where they are associated with objects stored in the page context (see Tags That Define Scripting Variables).

attribute Element

For each tag attribute, specify whether the attribute is required, whether the value can be determined by an expression, and, optionally, the type of the attribute in an attribute element. For static values the type is always java.lang.String. If the rtexprvalue element is true or yes, then the type element defines the return type expected from any expression specified as the value of the attribute.

<attribute>
   <name>attr1</name>
   <required>true|false|yes|no</required>
   <rtexprvalue>true|false|yes|no</rtexprvalue>
   <type>fully_qualified_type</type>
</attribute>

If a tag attribute is not required, a tag handler should provide a default value.

The tag element for the logic:present tag declares that the parameter attribute is not required (because the tag can also test for the presence of other entities such as bean properties) and that its value can be set by a runtime expression.

<tag>
   <name>present</name>
   <tag-class>org.apache.struts.taglib.
      logic.PresentTag</tag-class>
   <body-content>JSP</body-content>
   ...
   <attribute>
      <name>parameter</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
   </attribute>
   ...
</tag>

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 with the isValid method of a class derived from TagExtraInfo. This class is also used to provide information about scripting variables defined by the tag (see Tags That Define Scripting Variables).

The isValid method is passed the attribute information in a TagData object, which contains attribute-value tuples for each of the tag's attributes. Since the validation occurs at translation time, the value of an attribute that is computed at request time will be set to TagData.REQUEST_TIME_VALUE.

The tag <tt:twa attr1="value1"/> has the following TLD attribute element:

<attribute>
   <name>attr1</name>
   <required>true</required>
   <rtexprvalue>true</a>
</attribute>

This declaration indicates that the value of attr1 can be determined at runtime.

The following isValid method checks that the value of attr1 is a valid Boolean value. Note that since the value of attr1 can be computed at runtime, isValid must check whether the tag user has chosen to provide a runtime value.

public class TwaTEI extends TagExtraInfo {
   public boolean isValid(Tagdata data) {
      Object o = data.getAttribute("attr1");
      if (o != null && o != TagData.REQUEST_TIME_VALUE) {
         if (o.toLowerCase().equals("true") || 
            o.toLowerCase().equals("false") ) 
            return true;
         else
            return false;
      }
      else
         return true;
   }
}

 

Tags With Bodies

Tag Handlers

A tag handler for a tag with a body is implemented differently depending on whether the tag handler needs to interact with the body or not. By interact, we mean that the tag handler reads or modifies the contents of the body.

Tag Handler Does Not Interact with the Body

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 needs to return EVAL_BODY_INCLUDE; otherwise, it should return SKIP_BODY.

If a tag handler needs to iteratively evaluate the body, it should implement the IterationTag interface or be derived from TagSupport. It should return EVAL_BODY_AGAIN from the doStartTag and doAfterBody methods if it determines that the body needs to be evaluated again.

Tag Handler Interacts with the 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 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 EVAL_BODY_BUFFERED; otherwise, it should return SKIP_BODY.

doInitBody Method

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.

doAfterBody Method

The doAfterBody method is called after the body content is evaluated. Like the doStartTag method, 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, doAfterBody should return EVAL_BODY_BUFFERED; otherwise doAfterBody should return SKIP_BODY.

release Method

A tag handler should reset its state and release any private resources in the release method.

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, doAfterBody returns 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;
   }
}

body-content Element

For tags that have a body, specify the type of the body content using the body-content element:

<body-content>JSP|tagdependent</body-content>

Body content containing custom and core tags, scripting elements, and HTML text is categorized as JSP. This is the value declared for the Struts logic:present tag. All other types of body content--for example, SQL statements passed to the query tag--would be labeled tagdependent.

Note that the value of the body-content element does not affect the interpretation of the body by the tag handler; the element is only intended to be used by an authoring tool for rendering the body content.

 

Tags That Define Scripting Variables

Tag Handlers

A tag handler is responsible for creating and setting the object referred to by the scripting variable into a context accessible from the page. It does this by using the pageContext.setAttribute(name, value, scope) or pageContext.setAttribute(name, value) methods. Typically, an attribute passed to the custom tag specifies the name of the scripting variable object; this name can be retrieved by invoking the attribute's get method described in Defining Attributes in a Tag Handler.

If the value of the scripting variable is dependent on an object present in the tag handler's context, it can retrieve the object using the pageContext.getAttribute(name, scope) method.

The usual procedure is that the tag handler retrieves a scripting variable, performs some processing on the object, and then sets the scripting variable's value using the pageContext.setAttribute(name, object) method.

The scope that an object can have is summarized in Table 13-4. The scope constrains the accessibility and lifetime of the object.

 

Table 13-4 Scope of Objects 
Name
Accessible From
Lifetime
page Current page Until the response has been sent back to the user or the request is passed to a new page
request Current page and any included or forwarded pages Until the response has been sent back to the user
session Current request and any subsequent request from the same browser (subject to session lifetime) The life of the user's session
app Current and any future request from the same Web app The life of the app

Providing Information about the Scripting Variable

The example described in Tags That Define Scripting Variables defines a scripting variable book that is used for accessing book information:

<bean:define id="book" name="bookDB" property="bookDetails"
   type="database.BookDetails"/>
<font color="red" size="+2">
   <%=messages.getString("CartRemoved")%>
   <strong><jsp:getProperty name="book"
         property="title"/></strong> 
<br>&nbsp;<br>
</font>

When the JSP page containing this tag is translated, the Web container generates code to synchronize the scripting variable with the object referenced by the variable. To generate the code, the Web container requires certain information about the scripting variable:

  • Variable name
  • Variable class
  • Whether the variable refers to a new or existing object
  • The availability of the variable

There are two ways to provide this information: by specifying the variable TLD subelement or by defining a tag extra info class and including the tei-class element in the TLD. Using the variable element is simpler, but slightly less flexible.

variable Element

The variable element has the following subelements:

  • name-given: The variable name as a constant.
  • name-from-attribute: The name of an attribute whose translation-time value will give the name of the variable.

One of name-given or name-from-attribute is required. The following subelements are optional:

  • variable-class: The fully qualified name of the class of the variable. java.lang.String is the default.
  • declare: Whether the variable refers to a new object. True is the default.
  • scope: The scope of the scripting variable defined. NESTED is default. Table 13-5 describes the availability of the scripting variable and the methods in which the value of the variable must be set or reset.

The implementation of the Struts bean:define tag conforms to the JSP specification version 1.1, which requires you to define a tag extra info class. The JSP specification version 1.2 adds the variable element. You could define the following variable element for the bean:define tag:

<tag>
   <variable>
      <name-from-attribute>id</name-from-attribute>
      <variable-class>database.BookDetails</variable-class>
      <declare>true</declare>
      <scope>AT_BEGIN</scope>
   </variable>
</tag>

 

Table 13-5 Scripting Variable Availability 
Value Availability Methods
NESTED Between the start tag and the end tag In doInitBody and doAfterBody for a tag handler implementing BodyTag; otherwise, in doStartTag
AT_BEGIN From the start tag until the end of the page In doInitBody, doAfterBody, and doEndTag for a tag handler implementing BodyTag; otherwise, in doStartTag and doEndTag
AT_END After the end tag until the end of the page In doEndTag
TagExtraInfo Class

You define a tag extra info class by extending the class javax.servlet.jsp.TagExtraInfo. A TagExtraInfo must implement the getVariableInfo method to return an array of VariableInfo objects containing the following information:

  • Variable name
  • Variable class
  • Whether the variable refers to a new object
  • The availability of the variable

The Web container passes a parameter called data to the getVariableInfo method that contains attribute-value tuples for each of the tag's attributes. These attributes can be used to provide the VariableInfo object with a scripting variable's name and class.

The Struts tag library provides information about the scripting variable created by the bean:define tag in the DefineTei tag extra info class. Since the name (book) and class (database.BookDetails) of the scripting variable are passed in as tag attributes, they can be retrieved with the data.getAttributeString method and used to fill in the VariableInfo constructor. To allow the scripting variable book to be used in the rest of the page, the scope of book is set to be AT_BEGIN.

public class DefineTei 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("id"),
            type,
            true,
            VariableInfo.AT_BEGIN)
      };
   }
}

The fully qualified name of the tag extra info class defined for a scripting variable must be declared in the TLD in the tei-class subelement of the tag element. Thus, the tei-class element for DefineTei would be as follows:

<tei-class>org.apache.struts.taglib.bean.DefineTagTei
</tei-class>

 

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 both JSP pages and 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 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 connection has been set in the doStartTag method. 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 {
   private String connectionId;
   public int doStartTag() throws JspException {
      String cid = getConnection();
      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 id="con01" ....> ... </tt:connection>
<tt:query id="balances" connection="con01"> 
   SELECT account, balance FROM acct_table 
      where customer_number = <%= request.getCustno()%> 
</tt:query>

<tt:connection ...>
   <x:query id="balances"> 
      SELECT account, balance FROM acct_table 
         where customer_number = <%= request.getCustno()%> 
   </x:query>
</tt:connection>

The TLD for the tag handler must indicate that the connection attribute is optional with the following declaration:

<tag>
   ...
   <attribute>
      <name>connection</name>
      <required>false</required>
   </attribute>
</tag>

 


Examples

The custom tags described in this section demonstrate solutions to two recurring problems in developing JSP apps: minimizing the amount of Java programming in JSP pages and ensuring a common look and feel across apps. 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.

The Struts logic:iterate tag retrieves objects from a collection stored in a JavaBeans component and assigns them to a scripting variable. The body of the tag retrieves information from the scripting variable. While elements remain in the collection, the iterate tag causes the body to be reevaluated.

JSP Page

Two Duke's Bookstore app pages, catalog.jsp and showcart.jsp, use the logic:iterate tag to iterate over collections of objects. An excerpt from catalog.jsp is shown below. The JSP page initializes the iterate tag with a collection named(by the property attribute) of the bookDB bean. The iterate tag sets the book scripting variable on each iteration over the collection. The bookId property of the book variable is exposed as another scripting variable. Properties of both variables are used to dynamically generate a table containing links to other pages and book catalog information.

<logic:iterate name="bookDB" property="books" 
   id="book" type="database.BookDetails">
   <bean:define id="bookId" name="book" property="bookId"
      type="java.lang.String"/>

   <tr> 
   <td> 
   <a href="<%=request.getContextPath()%>
      /bookdetails?bookId=<%=bookId%>">
      <strong><jsp:getProperty name="book"
      property="title"/>&nbsp;</strong></a></td> 

   <td rowspan=2> 
   <jsp:setProperty name="currency" property="amount"
      value="<%=book.getPrice()%>"/>
   <jsp:getProperty name="currency" property="format"/>
   &nbsp;</td> 

   <td rowspan=2> 
   <a href="<%=request.getContextPath()%>
      /catalog?Add=<%=bookId%>">
      &nbsp;<%=messages.getString("CartAdd")%>
      &nbsp;</a></td></tr> 

   <tr> 
   <td> 
   &nbsp;&nbsp;<%=messages.getString("By")%> <em>
      <jsp:getProperty name="book"
         property="firstName"/>&nbsp;
      <jsp:getProperty name="book"
         property="surname"/></em></td></tr>
</logic:iterate>

Tag Handler

The implementation of the Struts logic:iterate tag conforms to the capabilities of the JSP version 1.1 specification, which requires you to extend the BodyTagSupport class. The JSP version 1.2 specification adds features (described in the section Tag Handler Does Not Interact with the Body) that simplify programming tags that iteratively evaluate their body. The following discussion is based on an implementation that uses these features.

The logic:iterate tag supports initializing the collection in several ways: from a collection provided as a tag attribute or from a collection that is a bean or a property of a bean. Our example uses the latter method. Most of the code in doStartTag is concerned with constructing an iterator over the collection object. The method first checks if the handler's collection property is set and, if not, proceeds to check the bean and property attributes. If the bean and property attributes are both set, doStartTag calls a utility method that uses JavaBeans introspection methods to retrieve the collection. Once the collection object is determined, the method constructs the iterator.

If the iterator contains more elements, doStartTag sets the value of the scripting variable to the next element and then indicates that the body should be evaluated; otherwise, it ends the iteration by returning SKIP_BODY.

After the body has been evaluated, the doAfterBody method retrieves the body content and writes it to the out stream. The body content object is then cleared in preparation for another body evaluation. If the iterator contains more elements, doAfterBody again sets the value of the scripting variable to the next element and returns EVAL_BODY_AGAIN to indicate that the body should be evaluated again. This causes the reexecution of doAfterBody. When there are no remaining elements, doAfterBody terminates the process by returning SKIP_BODY.

public class IterateTag extends TagSupport {
   protected Iterator iterator = null;
   protected Object collection = null;
   protected String id = null;
   protected String name = null;
   protected String property = null;
   protected String type = null;
   public int doStartTag() throws JspException {
      Object collection = this.collection;
      if (collection == null) {
         try {
            Object bean = pageContext.findAttribute(name);
            if (bean == null) {
               ... throw an exception
            }
            if (property == null)
               collection = bean;
            else
               collection =
                  PropertyUtils.
                     getProperty(bean, property);
            if (collection == null) {
               ... throw an exception
            }
         } catch 
            ... catch exceptions thrown 
               by PropertyUtils.getProperty
         }
      }
      // Construct an iterator for this collection
      if (collection instanceof Collection)
         iterator = ((Collection) collection).iterator();
      else if (collection instanceof Iterator)
         iterator = (Iterator) collection;
         ...
      }
      // Store the first value and evaluate, 
      // or skip the body if none
      if (iterator.hasNext()) {
         Object element = iterator.next();
         pageContext.setAttribute(id, element);
         return (EVAL_BODY_AGAIN);
      } else
         return (SKIP_BODY);
}
   public int doAfterBody() throws JspException {
      if (bodyContent != null) {
         try {
            JspWriter out = getPreviousOut();
            out.print(bodyContent.getString());
            bodyContent.clearBody();
         } catch (IOException e) {
            ...
         }
      }
      if (iterator.hasNext()) {
         Object element = iterator.next();
         pageContext.setAttribute(id, element);
         return (EVAL_BODY_AGAIN);
      } else
         return (SKIP_BODY);
      }
   }
}

Tag Extra Info Class

Information about the scripting variable is provided in the IterateTei tag extra info class. The name and class of the scripting variable are passed in as tag attributes and used to fill in the VariableInfo constructor.

public class IterateTei 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("id"),
         type,
         true,
         VariableInfo.AT_BEGIN)
      };
   }
}

 

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 app. 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 with 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 could 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, and parameter--to define a table of screen definition for an app screen and uses an insert tag to insert parameters from a screen definition into the app screen.

JSP Page

The template for the Duke's Bookstore example, template.jsp, is shown on the next page. This page includes a JSP page that creates the screen definition and then uses the insert tag to insert parameters from the definition into the app screen.

<%@ taglib uri="/tutorial-template.tld" prefix="tt" %>
<%@ page errorPage="errorpage.jsp" %>
<%@ include file="screendefinitions.jsp" %><html>
   <head>
      <title>
         <tt:insert definition="bookstore"
            parameter="title"/>
      </title>
   </head>
      <tt:insert definition="bookstore"
         parameter="banner"/>
      <tt:insert definition="bookstore" 
         parameter="body"/>
   </body>
</html>

screendefinitions.jsp creates a screen definition based on a request attribute selectedScreen:

<tt:definition name="bookstore" 
   screen="<%= (String)request.
      getAttribute(\"selectedScreen\") %>">
   <tt:screen id="/enter">
      <tt:parameter name="title" 
         value="Duke's Bookstore" direct="true"/>
      <tt:parameter name="banner" 
         value="/banner.jsp" direct="false"/>
      <tt:parameter name="body" 
         value="/bookstore.jsp" direct="false"/>
   </tt:screen>
   <tt:screen id="/catalog">
      <tt:parameter name="title" 
      value="<%=messages.getString("TitleBookCatalog")%>"
      direct="true"/>
      ...
</tt:definition>

The template is instantiated by the Dispatcher servlet. Dispatcher first gets the requested screen and stores it as an attribute of the request. This is necessary because when the request is forwarded to template.jsp, the request URL doesn't contain the original request (for example, /bookstore3/catalog) but instead reflects the path (/bookstore3/template.jsp) of the forwarded page. Finally, the servlet dispatches the request to template.jsp:

public class Dispatcher extends HttpServlet {
   public void doGet(HttpServletRequest request, 
         HttpServletResponse response) {
      request.setAttribute("selectedScreen",
         request.getServletPath());
      RequestDispatcher dispatcher =
         request.getRequestDispatcher("/template.jsp");
      if (dispatcher != null)
         dispatcher.forward(request, response);
   }
   public void doPost(HttpServletRequest request, 
            HttpServletResponse response) {
      request.setAttribute("selectedScreen",
         request.getServletPath());
      RequestDispatcher dispatcher =
         request.getRequestDispatcher("/template.jsp");
      if (dispatcher != null)
         dispatcher.forward(request, response);
   }
}

Tag Handlers

The template tag library contains four tag handlers--DefinitionTag, ScreenTag, ParameterTag, and InsertTag--that demonstrate the use of cooperating tags. DefinitionTag, ScreenTag, and ParameterTag comprise a set of nested tag handlers that share public and private objects. DefinitionTag creates a public named object called definition that is used by InsertTag.

In doStartTag, DefinitionTag creates a public object named screens that contains a hash table of screen definitions. A screen definition consists of a screen identifier and a set of parameters associated with the screen.

public int doStartTag() {
   HashMap screens = null;
   screens = (HashMap) pageContext.getAttribute("screens",
      pageContext.APPLICATION_SCOPE);
   if (screens == null)
      pageContext.setAttribute("screens", new HashMap(), 
         pageContext.APPLICATION_SCOPE);
   return EVAL_BODY_INCLUDE;
}

The table of screen definitions is filled in by ScreenTag and ParameterTag from text provided as attributes to these tags. Table 13-6 shows the contents of the screen definitions hash table for the Duke's Bookstore app

 

Table 13-6 Screen Definitions 
Screen Id
Title
Banner
Body
/enter Duke's Bookstore /banner.jsp /bookstore.jsp
/catalog Book Catalog /banner.jsp /catalog.jsp
/bookdetails Book Description /banner.jsp /bookdetails.jsp
/showcart Your Shopping Cart /banner.jsp /showcart.jsp
/cashier Cashier /banner.jsp /cashier.jsp
/receipt Receipt /banner.jsp /receipt.jsp
.

In doEndTag, DefinitionTag creates a public object of class Definition, selects a screen definition from the screens object based on the URL passed in the request, and uses it to initialize the Definition object.

public int doEndTag()throws JspTagException {
   try {
      Definition definition = new Definition();
      Hashtable screens = null;
      ArrayList params = null;
      TagSupport screen = null;
      screens = (HashMap) 
         pageContext.getAttribute("screens",
            pageContext.APPLICATION_SCOPE);
      if (screens != null)
         params = (ArrayList) screens.get(screenId);
      else
         ...
      if (params == null)
         ...
      Iterator ir = null;
      if (params != null)
         ir = params.iterator();
      while ((ir != null) && ir.hasNext())
         definition.setParam((Parameter) ir.next());
         // put the definition in the page context
      pageContext.setAttribute(
         definitionName, definition);
   } catch (Exception ex) {
      ex.printStackTrace();
   }
   return EVAL_PAGE;
}

If the URL passed in the request is /enter, the Definition contains the items from the first row of Table 13-6:

 

Title
Banner
Body
Duke's Bookstore /banner.jsp /bookstore.jsp

The definition for the URL /enter is shown in Table 13-7. The definition specifies that the value of the Title parameter, Duke's Bookstore, should be inserted directly into the output stream, but the values of Banner and Body should be dynamically included.

 

Table 13-7 Screen Definition for the URL /enter 
Parameter Name
Parameter Value
isDirect
title Duke's Bookstore true
banner /banner.jsp false
body /bookstore.jsp false

InsertTag uses Definition to insert parameters of the screen definition into the response. In the doStartTag method, it retrieves the definition object from the page context.

public int doStartTag() {
   // get the definition from the page context
   definition = (Definition) pageContext.
      getAttribute(definitionName);
   // get the parameter
   if (parameterName != null && definition != null)
      parameter = (Parameter)definition.
         getParam(parameterName);
   if (parameter != null)
      directInclude = parameter.isDirect();
   return SKIP_BODY;
}

The doEndTag method 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 int doEndTag()throws JspTagException {
   try {
      if (directInclude && parameter != null)
         pageContext.getOut().print(parameter.getValue());
      else {
         if ((parameter != null) && 
            (parameter.getValue() != null))
            pageContext.include(parameter.getValue());
      }
   } catch (Exception ex) {
      throw new JspTagException(ex.getMessage());
   }
   return EVAL_PAGE;
}

 


How Is a Tag Handler Invoked?

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 Tag by defining additional methods that let a tag handler access its body. The interface provides three new methods:

  • setBodyContent: Creates body content and adds to the tag handler
  • doInitBody: Called before evaluation of the tag body
  • doAfterBody: Called after evaluation of the tag body

A typical invocation sequence is as follows:

t.doStartTag();
out = pageContext.pushBody();
t.setBodyContent(out);
// perform any initialization needed after body content is set
t.doInitBody();
t.doAfterBody();
// while doAfterBody returns EVAL_BODY_BUFFERED we 
// iterate body evaluation
...
t.doAfterBody();
t.doEndTag();
t.pageContext.popBody();
t.release();