[ Team LiB ] Previous Section Next Section

21.1 Developing Simple Tag Handlers

The simple tag handler is new as of JSP 2.0. Don't be fooled by the name; a simple tag handler can implement complex behavior for a custom action, such as conditional evaluation of its body, iteration over its body any number of times, and processing of the body evaluation result. The name refers to the implementation task, which is indeed much simpler than it was for the type of tag handlers supported in previous versions of the JSP specification.

The simplifications are made possible by prohibiting the use of scripting elements (Java code) in the custom action's body. This would have been met with a great deal of resistance not too long ago, but with the introduction of the EL and JSTL, you rarely (if ever) need to use scripting elements. If this restriction is not acceptable for your application, you must implement the custom action using a classic tag handler instead.

A simple tag handler implements the javax.servlet.jsp.tagext.SimpleTag interface. This interface has five methods, but most tag handlers just extend a base class named javax.servlet.jsp.tagext.SimpleTagSupport and inherit implementations of all but one method: doTag( ). In addition, the tag handler must implement standard JavaBeans setter methods for all of its custom action attributes (if it has any, of course).

As you may recall, a tag library is a collection of custom actions. For instance, all custom actions used in this book are packaged as one tag library. Besides the tag-handler class files, a tag library contains a Tag Library Descriptor (TLD) file. This is an XML file that maps all custom action names to the corresponding tag handlers and describes all attributes supported by each custom action. The class files and the TLD can be packaged in a JAR file to make installation easier. We look at the TLD syntax and packaging details at the end of this chapter.

Before getting into all the intricate details, let's use a simple example to see what it takes to develop, deploy, and use a custom action using a simple tag handler. First, you implement the tag handler class:

package com.mycompany;
  
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
  
public class HelloTag extends SimpleTagSupport {
    private String name = "World";
  
    public void setName(String name) {
        this.name = name;
    }
  
    public void doTag(  ) throws IOException{
        getJspContext().getOut(  ).println("Hello " + name);
    }
}

The tag handler class extends the SimpleTagSupport class to get most of the SimpleTag interface methods implementations for free. It implements a setter method only for an attribute called name and the doTag( ) method. The doTag( ) method (defined by the SimpleTag interface) simply writes "Hello" plus the name attribute value to the response. Note that the tag handler class must be part of a package, for the same reason a bean must be part of a package, described in detail in Chapter 16: classes in the default, unnamed package cannot be used in a class that belongs to a package (such as the class generated for the JSP page).

To compile the class, include the servlet and JSP API classes in your classpath. The API classes are distributed with all compliant containers. For Tomcat, you find them in the servlet-api.jar and jsp-api.jar files located in the common/lib directory under the Tomcat installation directory. When you have compiled the tag handler, place the class file in the WEB-INF/classes directory structure for the application so the container can find it.

Next, you create the TLD file. The following is a minimal TLD file for a library with just one custom action element:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
  version="2.0">
  
  <tlib-version>1.0</tlib-version>
  <short-name>test</short-name>
  <uri>com.mycompany.mylib</uri>
  
  <tag>
    <name>hello</name>
    <tag-class>com.mycompany.HelloTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
      <name>name</name>
    </attribute>
  </tag>
</taglib>

The TLD maps the custom action name hello to the tag handler class com.mycompany.HelloTag and defines the name attribute. Place the TLD file in the application's WEB-INF/tlds directory, for instance with the filename mylib.tld.

Now you're ready to use the custom action in a JSP page, like this:

<%@ taglib prefix="test" uri="com.mycompany.mylib" %>
<html>
  <body bgcolor="white">
    <test:hello name="Hans" />
  </body>
</html>

When the page is requested, the JSP container uses the taglib directive to find the TLD, and the TLD to figure out which class to execute for the custom action. It then calls all the appropriate methods, resulting in the text "Hello Hans" being added to the response. That's all there is to it for the most simple case. In the remainder of this chapter, we go through all of this in greater detail.

21.1.1 Accessing Context Information

As you have seen in the previous chapters, a custom action element in a JSP page consists of a start tag (possibly with attributes), optionally a body, and an end tag:

<prefix:actionName attr1="value1" attr2="value2">
  The body
</prefix:actionName>

If the action element doesn't have a body, the following shorthand notation can be used instead:

<prefix:actionName attr1="value1" attr2="value2" />

A tag handler implements the custom action's behavior. When the container encounters a custom action, it creates an instance of the corresponding tag handler class, based on the information declared in the TLD.

In order for the tag handler to do anything interesting, it needs access to context information, such as the request and scope information, as well as the action element's attribute values (if any). The container calls methods defined in the SimpleTag interface to provide this information. The container calls the setJspContext( ) method to provide the context information in the form of a JspContext instance. For the attribute values, the JSP container treats the tag handler as a bean and calls a setter method for each attribute. When the tag handler has been initialized, the container asks it to do its thing by calling the doTag( ) method, as shown in Figure 21-1.

Figure 21-1. SimpleTag interface methods and property setter methods
figs/Jsp3_2101.gif

Here are the SimpleTag interface methods of importance for all simple tag handlers:

public void setJspContext(JspContext jspContext);
public void doTag(  ) throws JspException;

Most simple tag handlers extend the SimpleTagSupport class, and its implementation of the setJspContext( ) method simply saves a reference to the instance in a private instance variable named jspContext, where it can then be accessed by a subclass by calling the corresponding getter method implemented by the SimpleTagSupport class: getJspContext( ).

The JspContext provides access to all the JSP scope variables and the current output stream for the page, and it implements a number of utility methods the tag handler may use. We use most of these methods in the examples in this chapter. Appendix D includes a complete list of all JspContext methods.


The container calls the doTag( ) method when the tag handler has been initialized. The SimpleTagSupport implementation of this method does nothing, so you have to provide an implementation in the subclass.

Let's implement the tag handler for the <ora:addCookie> action, introduced in Chapter 13, to get a better idea of how all this works. The tag handler class is called com.ora.jsp.tags.AddCookieTag and extends the SimpleTagSupport class to inherit most of the SimpleTag interface method implementations:

package com.ora.jsp.tags;

import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import com.ora.jsp.util.*;

public class AddCookieTag extends SimpleTagSupport {

The <ora:addCookie> action has two mandatory attributes, name and value, and one optional attribute, maxAge. Each attribute is represented by an instance variable and a standard property setter method:

    private String name;
    private String value;
    private String maxAgeString;

    public void setName(String name) {
        this.name = name;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public void setMaxAge(String maxAgeString) {
        this.maxAgeString = maxAgeString;
    }

The purpose of the custom action is to create a new javax.servlet.http.Cookie object with the name, value, and maximum age values specified by the attributes, and to add the cookie to the response. The tag handler class overrides the doTag( ) method to carry out this work:

    public void doTag(  ) throws JspException {
        int maxAge = -1;
        if (maxAgeString != null) {
            try {
                maxAge = Integer.valueOf(maxAgeString).intValue(  );
            }
            catch (NumberFormatException e) {
                throw new JspTagException("Invalid maxAge", e);
            }
        }
        PageContext pageContext = (PageContext) getJspContext(  );
        HttpServletResponse response = 
            (HttpServletResponse) pageContext.getResponse(  );
        CookieUtils.sendCookie(name, value, maxAge, response);
    }
}

The maxAge attribute is optional, so before the corresponding String value is converted into an int, a test is performed to see if it's set or not. The name and value attributes are declared as mandatory in the TLD. The JSP container refuses to process the page if the mandatory attributes are not set, so you can always be sure that variables corresponding to mandatory attributes have values.

The JspContext class is an abstraction introduced in JSP 2.0 to allow the simple tag handler machinery to be used in nonservlet environment in the future. It provides access to scoped variables, but you don't find the specific scopes (page, request, session, and application) defined here. Nor do you find access methods for the request, the response, or other servlet specific objects. These things are instead defined in the PageContext class, which is a subclass of JspContext. In a servlet-based JSP container (the only type of container currently available), the context object passed to the tag handler through the setJspContext( ) method is always an instance of PageContext. To get the response object needed as an argument to the sendCookie( ) method, I call getJspContext( ) and cast the returned context object to PageContext so I can call its getResponse( ) method.

The code that actually creates the Cookie object and adds it to the response object is performed by the sendCookie( ) method in the com.ora.jsp.util.CookieUtils class. This is a common practice. The utility class knows nothing about JSP, so it can be used in other environments such as servlets and applets. The tag handler acts as a simple adapter for the reusable environment agnostic class, getting all information it needs about the request, the response, and all the variables in the JSP scopes through the PageContext.

The sendCookie( ) method is implemented like this in the CookieUtils class:

public static void sendCookie(String name, String value, int maxAge,
    HttpServletResponse res) {
  
    Cookie cookie = new Cookie(name, value);
    cookie.setMaxAge(maxAge);
    res.addCookie(cookie);
}

The sendCookie( ) method and the <ora:addCookie> custom action could be improved to handle other cookie attributes, such as the domain and path. I leave that as an exercise that you may want to do if you use these classes in your applications.

21.1.2 Aborting the Page Processing

For some custom actions, the processing of the page must stop after the custom action has been processed. An example is a custom action that redirects or forwards to another page, such as the JSTL <c:redirect> action.

A simple tag handler can throw a javax.servlet.jsp.tagext.SkipPageException to signal to the container that the rest of the page must not be evaluated. The container respects this no matter how deeply the custom action is nested within bodies of other actions. To show how it's done, here's a simple tag handler with the sole purpose of aborting the page processing:

package com.ora.jsp.tags.xmp;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class AbortPageTag extends SimpleTagSupport {
    public void doTag(  ) throws JspException {
        throw new SkipPageException(  );
    }
}

You could use this feature to, for instance, develop a smart forwarding action that decides which page to forward to based on runtime conditions, such as the time of the day, the current user, or the type of browser accessing the page. After the forwarding call, throwing the SkipPageException terminates the processing of the rest of the page that contains the forwarding action.

21.1.3 Processing the Action Body as an Executable Fragment

An action element's body can contain other actions, EL expressions, and template text. The body can be used for input values spanning multiple lines; the JSTL database actions described in Chapter 12 use the body this way. The SQL statement is often large, so it's cleaner to let the page author write it in the action body instead of as an attribute value. A similar example is an action that processes the body content in one way or another before it's added to the response. Chapter 15 shows how the JSTL <x:transform> action processes its XML body using the XSL stylesheet specified as an attribute.

Some actions do not really use the body as input but process it in other ways. One example is a conditional custom action, such as the JSTL <c:if> action, which only passes the body through if a runtime condition is met. A custom action that processes the dynamic elements in the body a number of times, like the JSTL <c:forEach> action, is another example.

You can use simple tag handlers to implement all these types of custom actions. The key to unlocking this magic lies in what's called a JSP fragment and a SimpleTag method I haven't told you about so far:

public void setJspBody(JspFragment body)

The container calls this method with a reference to a JSP fragment representing the custom action body before calling doTag( ). If the custom action doesn't have a body, this method is not called at all.

A JSP fragment is an internal representation of dynamic JSP elements (actions and EL expressions), possibly mixed with template text. The container converts the body of a custom action implemented as a simple tag handler to this internal format and exposes it to the tag handler as an object of the type javax.servlet.jsp.tagext.JspFragment. This fragment is associated with the JspContext for the page where it's defined, so the dynamic elements in the fragment access the same scoped variables and request and response objects as the page. The tag handler invokes the fragment by calling the invoke( ) method on the JspFragment instance. Invoking the fragment means that all the dynamic elements in the fragment are executed, and the output they produce is combined with the template text, if any, to form a textual evaluation result. Since the elements in the fragment have access to the current values of all scoped variables, the result typically differs from invocation to invocation.

21.1.3.1 Conditional and iterating processing

A tag handler for a custom action that only processes the body based on some condition should call the invoke( ) method on the fragment representing the body only if the given condition is true. Example 21-1 shows how a somewhat simplified version of the JSTL <c:if> action could be implemented as a simple tag handler.

Example 21-1. Conditional tag handler (IfTag.java)
package com.ora.jsp.tags.xmp;

import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class IfTag extends SimpleTagSupport {
    private boolean test;

    public void setTest(boolean test) {
        this.test = test;
    }

    public void doTag(  ) throws JspException, IOException {
        if (test && getJspBody(  ) != null) {
            getJspBody(  ).invoke(null);
        }
    }
}

As I described earlier, the container calls the setJspBody( ) method to give the tag handler a reference to the body fragment. This method is implemented by the SimpleTagSupport class and saves the reference in a private instance variable. The getJspBody( ) method—used in the doTag( ) method in this example—is also inherited from the SimpleTagSupport class, and it simply returns the reference.

In the tag handler subclass, implement a setter method for the test attribute, and in the doTag( ) method, call the invoke( ) method on the body fragment if the condition specified as the test attribute value is true. Note that you must also check that getJspBody( ) returns a fragment to prevent a NullPointerException in case the custom action is used without a body. You can pass a java.io.Writer to the invoke( ) method to capture the output, as I will show you later. When you pass it null, as I do here, the output is added to the response stream for the page that contains the custom action.

Implementing a tag handler for an iterating custom action, similar to <c:forEach>, is almost as easy: just call the body fragment's invoke( ) method for each pass through the iteration until the end condition is reached. In most cases, an iterating tag handler also makes the current item available as a page scope variable, so the elements in the body can use it to produce different results for each iteration.

To illustrate how an iterating custom action works, let's implement a scaled-down version of the <c:forEach> action that supports only Collection data structures and call this action <ora:simpleLoop>. It can, for instance, be used like this with a Collection that contains beans with firstName and lastName properties:

<%@ page contentType="text/html" %> 
<%@ taglib prefix="ora" uri="orataglib" %>
  
<ul>
  <ora:simpleLoop items="${myCollection}" var="current">
    <li>
      ${current.lastName}, ${current.firstName}
    </li>
  </ora:simpleLoop>
</ul>

The custom action iterates through the collection and exposes the current element as a page scoped variable named by the var attribute. The body contains two EL expressions and some template text. Since the EL expressions refer to properties of the variable containing the current element, each pass through it produces a different result.

The tag handler class for the <ora:simpleLoop> action is shown in Example 21-2.

Example 21-2. Iteration tag handler (SimpleLoopTag.java)
package com.ora.jsp.tags.xmp;

import java.io.*;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class SimpleLoopTag extends SimpleTagSupport {
    private Collection items;
    private String var;
    
    public void setItems(Collection items) {
        this.items = items;
    }
    
    public void setVar(String var) {
        this.var = var;
    }

    public void doTag(  ) throws JspException, IOException {
        JspFragment body = getJspBody(  );
        if (body != null) {
            PageContext pageContext = (PageContext) getJspContext(  );
            Iterator i = items.iterator(  );
            while (i.hasNext(  )) {
                Object currValue = i.next(  );
                getJspContext(  ).setAttribute(var, currValue);
                body.invoke(null);
            }
        }
    }
}

There's really nothing to this that you haven't seen before. The tag handler extends the SimpleTagSupport class, so it inherits the setJspBody( ) and getJspBody( ) methods, described earlier. It implements setter methods for the two mandatory attributes: items and var. The doTag( ) method first verifies that there's indeed a body to evaluate and, if so, gets an Iterator for the collection. For each element in the collection, it saves the current element as a page scope variable and calls invoke( ) on the body fragment.

21.1.3.2 Processing the action body

As you see, it's easy to implement a custom action as a simple tag handler, even one that conditionally evaluates the body 0 or any number of times. To develop a tag handler that reads and processes the result of the body evaluation, we just need one more thing: a way to capture the result of this evaluation.

Let's look at a tag handler class for the <ora:menuItem> custom action introduced in Chapter 17. As you may remember, this action reads its body and wraps it with an HTML link element if the specified page isn't the current page. Here's how the action can be used for a navigation bar, included in the main pages for an application:

<%@ page contentType="text/html" %>
<%@ taglib prefix="ora" uri="orataglib" %>
<table bgcolor="lightblue">
  <tr>
    <td>
      <ora:menuItem page="page1.jsp">
        <b>Page 1</b>
      </ora:menuItem>
    </td>
  </tr>
  <tr>
    <td>
      <ora:menuItem page="page2.jsp">
        <b>Page 2</b>
      </ora:menuItem>
    </td>
  </tr>
  <tr>
    <td>
      <ora:menuItem page="page3.jsp">
        <b>Page 3</b>
      </ora:menuItem>
    </td>
  </tr>
</table>

In this example, the contents of the custom action element is plain HTML template text, but as before, it could also contain other actions and EL expressions.

The tag handler class for the <ora:menuItem> action is shown in Example 21-3.

Example 21-3. Tag handler reading body evaluation (MenuItemTag.java)
package com.ora.jsp.tags;

import java.io.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import com.ora.jsp.util.StringFormat;

public class MenuItemTag extends SimpleTagSupport {
    private String page;

    public void setPage(String page) {
        this.page = page;
    }
    
    public void doTag(  ) throws JspException, IOException {
        JspFragment body = getJspBody(  );
        if (body == null) {
            throw new JspTagException("'menuItem' used without a body");
        }

        PageContext pageContext = (PageContext) getJspContext(  );
        HttpServletRequest request = 
            (HttpServletRequest) pageContext.getRequest(  );
        String requestURI = request.getServletPath(  );
        // Convert the specified page URI to a context-relative URI
        String pageURI = StringFormat.toContextRelativeURI(page, requestURI);

        if (requestURI.equals(pageURI)) {
            // Add the body as is
            body.invoke(null);
        }
        else {
            // Add the body as the text of an HTML link to page
            String uri = request.getContextPath(  ) + pageURI;
            HttpServletResponse response = 
                (HttpServletResponse) pageContext.getResponse(  );

            StringWriter evalResult = new StringWriter(  );
            StringBuffer buff = evalResult.getBuffer(  );
            buff.append("<a href=\"").append(response.encodeURL(uri)).
                append("\">");
            body.invoke(evalResult);
            buff.append("</a>");
            getJspContext().getOut(  ).print(buff);
        }
    }
}

The action has one attribute named page, implemented by the tag handler as a setter method that saves the value in a private instance variable.

In the doTag( ) method, I first check if there's a body. Because this custom action doesn't make sense without a body, I throw an exception if it doesn't have one. If it has a body, the page attribute value is converted to a context-relative URI and compared to the URI for the current request. If they match, the text to be written by the action is set to the body content as is; if not, the body content is wrapped in an HTML link element, using the page attribute value as the href attribute value and the body content as the link text.

The most interesting part of this tag handler is how it reads the body content and writes its output to the current response stream. To capture the result of the body evaluation, a java.io.Writer is passed to the invoke( ) method. Here I use an instance of the StringWriter subclass. This allows me to get hold of the evaluation result through its internal StringBuffer and add the HTML link element start and stop tags around it. The combination of the tags and the body evaluation result is then written to the JspWriter returned by the context's getOut( ) method. We'll discuss the JspWriter in more detail when we look at classic tag handlers, but it's really just a representation of the Writer for the response.

21.1.4 Processing Fragment Attributes

In all the examples so far, the custom action attribute types have been either regular Java classes or primitive types. The container evaluates attributes like these once and passes the resulting value to the tag handler through the attribute setter methods; however, it's perfectly legal to declare an attribute of type JspFragment. In this case, the container does not evaluate the value. Instead it passes a fragment representation of the value to the setter method that the tag handler can evaluate as many times as needed, in exactly the same manner as the body fragment.

Fragment attributes make a lot of sense for a custom action where the page author should be able to specify dynamic templates, or patterns, that are applied to elements of a collection. One example is a custom action that iterates over the days in a month. The page author should be able to describe different templates for rendering weekdays than for weekends, for instance. Let's develop a tag handler for a custom action that does just that and more. Here's how the custom action can be used in a JSP page:

<table border="1" cellspacing="0">
  <caption>
    <fmt:formatDate value="${now}" pattern="MMMM yyyy" />
  </caption>
  <ora:calendar date="${now}" var="c">
    <jsp:attribute name="beforePattern">
      <tr>
    </jsp:attribute>
    <jsp:attribute name="afterPattern">
      </tr>
    </jsp:attribute>
    <jsp:attribute name="dayNamePattern">
      <th><fmt:formatDate value="${c}" pattern="EE" /></th>
    </jsp:attribute>
    <jsp:attribute name="padPattern">
      <td bgcolor="lightgrey" width="30" height="30" valign="top">
        <fmt:formatDate value="${c}" pattern="d" />
      </td>
    </jsp:attribute>
    <jsp:attribute name="weekdayPattern">
      <td bgcolor="lightblue" width="30" height="30" valign="top">
        <fmt:formatDate value="${c}" pattern="d" />
      </td>
    </jsp:attribute>
    <jsp:attribute name="weekendPattern">
      <td bgcolor="yellow" width="30" height="30" valign="top">
        <fmt:formatDate value="${c}" pattern="d" />
      </td>
    </jsp:attribute>
  </ora:calendar>
</table>

The <ora:calendar> custom action has two regular attributes: date, which must be set to a java.util.Date representing the month to render, and var, which can optionally specify a variable to hold a reference to a java.util.Date instance for the current day of the month the fragments are asked to process.

Fragment attributes are used for the different patterns, such as weekday and weekend patterns. All fragment attributes must be set using the <jsp:attribute> action. The beforePattern and afterPattern fragments are evaluated before and after the first and last day of a week, respectively. In this example, the calendar is rendered as an HTML table, so these attributes are set to table row begin and end elements. The dayNamePattern fragment is evaluated once for each day in a week, and the weekdayPattern and weekendPattern fragments are evaluated for each workday and weekend day in the month. The padPattern attribute, finally, is evaluated for the days of the previous month and the following month needed to produce full weeks. All these fragment attributes define a table cell with different background colors, containing just the date. You could, of course, put any dynamic content in these fragments, such as custom actions that add information about events scheduled for each day, pulled from a database or some other type of data source.

Allowing the page author to specify the different patterns like this is very flexible. The same custom action can be used to produce a completely different type of calendar:

<code>
  <fmt:formatDate value="${now}" pattern="MMMM yyyy" />
  <br>
  <ora:calendar date="${now}" var="c">
    <jsp:attribute name="afterPattern">
      <br>
    </jsp:attribute>
    <jsp:attribute name="padPattern">
      | ------ |
    </jsp:attribute>
    <jsp:attribute name="weekdayPattern">
      | <fmt:formatDate value="${c}" pattern="EE dd" /> |
    </jsp:attribute>
  </ora:calendar>
</code>

Here I use only the afterPattern, padPattern and weekDayPattern (which is then also used for weekend days) fragments to generate a simple ASCII calendar. Figure 21-2 shows how both versions look in a browser.

Figure 21-2. Different calendar layout produced by the same custom action using fragments for customization
figs/Jsp3_2102.gif

Pretty cool, huh? Let's see the code. Example 21-4 shows the first part of the tag handler class for the <ora:calendar> custom action.

Example 21-4. Setter methods for fragment attributes (MonthCalendarTag.java)
package com.ora.jsp.tags;

import java.util.*;
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class MonthCalendarTag extends SimpleTagSupport {
    private Date date;
    private String var;
    private JspFragment padPattern;
    private JspFragment beforePattern;
    private JspFragment afterPattern;
    private JspFragment dayNamePattern;
    private JspFragment weekdayPattern;
    private JspFragment weekendPattern;

    public void setDate(Date date) {
        this.date = date;
    }
    
    public void setVar(String  var) {
        this.var = var;
    }

    public void setBeforePattern(JspFragment beforePattern) {
        this.beforePattern = beforePattern;
    }
    
    public void setAfterPattern(JspFragment afterPattern) {
        this.afterPattern = afterPattern;
    }
    
    public void setPadPattern(JspFragment padPattern) {
        this.padPattern = padPattern;
    }
    
    public void setDayNamePattern(JspFragment dayNamePattern) {
        this.dayNamePattern = dayNamePattern;
    }
    
    public void setWeekdayPattern(JspFragment weekdayPattern) {
        this.weekdayPattern = weekdayPattern;
    }
    
    public void setWeekendPattern(JspFragment weekendPattern) {
        this.weekendPattern = weekendPattern;
    }

The type of all fragment attributes is javax.servlet.jsp.tagext.JspFragment. Fragment attributes must also be declared as such in the TLD for the tag library:

  <tag>
    <name>calendar</name>
    <tag-class>com.ora.jsp.tags.MonthCalendarTag</tag-class>
    <body-content>empty</body-content>
    ...
    <attribute>
      <name>padPattern</name>
      <required>false</required>
      <fragment>true</fragment>
    </attribute>
    ...

  </tag>

The default value for the <fragment> element is false, so if you leave it out, the attribute is handled as a standard attribute.

Example 21-5 shows the doTag( ) method, where the basic calendar processing control flow is implemented.

Example 21-5. Calendar processing control flow (MonthCalendarTag.java)
    public void doTag(  ) throws JspException, IOException {
        Calendar calendar = new GregorianCalendar(  );
        int firstDayOfWeek = calendar.getFirstDayOfWeek(  );

        if (dayNamePattern != null) {
            evalDayNamePattern(calendar, firstDayOfWeek);
        }

        calendar.setTime(date);
        calendar.set(Calendar.DAY_OF_MONTH, 1);

        if (padPattern != null) {
            evalPrePadPattern(calendar, firstDayOfWeek);
        }

        evalDayPatterns(calendar, firstDayOfWeek);

        if (padPattern != null) {
            evalPostPattern(calendar, firstDayOfWeek);
        }
    }

A java.util.GregorianCalendar instance drives the processing. If a dayNamePattern fragment is provided, it's evaluated once for each day in a week (e.g., Sunday through Saturday). Next, the calendar is set to the first day of the month specified by the date attribute. If there's a padPattern fragment and the first day of this month is not the first day of the week, the fragment is evaluated once for each day in the last week of the previous month to get a full first week. The weekdayPattern and weekendPattern (if any) fragments are then evaluated for all days of the specified month, and finally, the padPattern fragment is processed again for all days in the following month if needed to fill the last week.

The actual fragment evaluation is implemented in a number of private methods. Example 21-6 shows the method that evaluates the dayNamePattern fragment.

Example 21-6. Day name fragment evaluation method (MonthCalendarTag.java)
    private void evalDayNamePattern(Calendar calendar, int firstDayOfWeek) 
        throws JspException, IOException {
        if (beforePattern != null) {
            beforePattern.invoke(null);
        }
        for (int i = 0, day = firstDayOfWeek; i < 7; i++, day++) {
            calendar.set(Calendar.DAY_OF_WEEK, day);
            if (var != null) {
                getJspContext().setAttribute(var, calendar.getTime(  ));
            }
            dayNamePattern.invoke(null);
        }
        if (afterPattern != null) {
            afterPattern.invoke(null);
        }
    }

The method loops through the calendar seven times, from the first day of the week to the last. For each iteration, the method saves a java.util.Date instance representing the current calendar date as a page scope variable with the name specified by the var attribute. Dynamic elements in the day name pattern can use this variable to create a header with the day names, as shown in Figure 21-2. The fragment is evaluated just as the body fragment in the previous section, by calling the invoke( ) method. Because this tag handler doesn't need to process the result, null is passed as the argument value.

The rest of the fragment evaluation methods are very similar, as shown in Example 21-7.

Example 21-7. Remaining fragment evaluation methods (MonthCalendarTag.java)
    private void evalPrePadPattern(Calendar calendar, int firstDayOfWeek)  
        throws JspException, IOException {
        // Reset to start of week, possibly in the previous month
        int firstDayOfMonth = calendar.get(Calendar.DAY_OF_WEEK);
        calendar.add(Calendar.DATE, firstDayOfWeek - firstDayOfMonth);

        if (beforePattern != null) {
            beforePattern.invoke(null);
        }

        int padDays = firstDayOfMonth - firstDayOfWeek;
        for (int i = 0; i < padDays; i++) {
            if (var != null) {
                getJspContext().setAttribute(var, calendar.getTime(  ));
            }
            padPattern.invoke(null);
            calendar.add(Calendar.DAY_OF_WEEK, 1);
        }
    }

    private void evalDayPatterns(Calendar calendar, int firstDayOfWeek) 
        throws JspException, IOException {

        int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        int lastDayOfWeek = firstDayOfWeek - 1 == 0 ? 7 : firstDayOfWeek - 1;
        for (int i = 0; i < daysInMonth; i++) {
            if (var != null) {
                getJspContext().setAttribute(var, calendar.getTime(  ));
            }
            int day = calendar.get(Calendar.DAY_OF_WEEK);
            if (day == firstDayOfWeek && beforePattern != null) {
                beforePattern.invoke(null);
            }

            if ((day == Calendar.SATURDAY || day == Calendar.SUNDAY) &&
                weekendPattern != null) {
                weekendPattern.invoke(null);
            }
            else {
                weekdayPattern.invoke(null);
            }

            if (day == lastDayOfWeek && afterPattern != null) {
                afterPattern.invoke(null);
            }            
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }
    }

    private void evalPostPattern(Calendar calendar, int firstDayOfWeek) 
        throws JspException, IOException {
        while (calendar.get(Calendar.DAY_OF_WEEK) != firstDayOfWeek) {
            if (var != null) {
                getJspContext().setAttribute(var, calendar.getTime(  ));
            }
            padPattern.invoke(null);
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        if (afterPattern != null) {
            afterPattern.invoke(null);
        }
    }
}

All methods save the current date as a page scope variable and invoke the fragment corresponding to the pattern the method handles.

Fragment attributes are very handy for some types of custom actions, as you can see, and are very easy to use. If you're developing a custom action with multiple aspects that can be customized and processed a variable number of times, you should consider using fragment attributes.

21.1.5 Handling Exceptions

Methods called by a tag handler may throw exceptions. Exception handling in a simple tag handler is very easy compared to how it's done for a classic tag handler, since all processing takes place in the single doTag( ) method. You must catch exceptions and either deal with them, or rethrow them wrapped in a JspException or the JspTagException subclass. Both exception classes have the same types of constructors:

public JspTagException(  );
public JspTagException(String msg);
public JspTagException(String msg, Throwable rootCause);
public JspTagException(Throwable rootCause);

When you rethrow an exception that you have caught, use one of the two latter constructors and pass on the exception, since the root cause is often needed to figure out what the problem is. Most containers unwrap the exception chain and write stack traces for all of the exceptions in the chain to the application log file. When throwing exceptions, you should avoid the no-argument constructor, since it doesn't let you say what's wrong, and use the second one for exceptions generated internally in the tag handler, for instance to report an invalid input type or insufficient privileges.

An exception thrown by a custom action can be caught and handled in the JSP page with the JSTL <c:catch> action, as I described in Chapter 9. If it's not caught, it's handled by the JSP container by forwarding to a custom error page, if specified, or to a container default page.

Calling the invoke( ) method of a fragment may result in either a JspException or an IOException. Typically, the doTag( ) method is declared to throw these same exceptions, so if it's okay to just let them propagate, you don't need to do anything. The only time you really need to worry about catching exceptions thrown by a fragment is in tag handlers that use resources that must be closed, such as a file, or returned to a pool, such as a database connection. If you fail to catch exceptions thrown by a fragment in this type of tag handler, the application will eventually run out of its limited resources. To illustrate how to handle fragment exceptions, Example 21-8 shows the tag handler for an action that writes the result of the evaluation of its body to a file, identified by an attribute named fileName.

Example 21-8. A tag handler that handles exceptions properly
package com.ora.jsp.tags;

import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class FileWriteTag extends SimpleTagSupport {
    private String fileName;

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public void doTag(  ) throws JspException {
        JspFragment body = getJspBody(  );
        if (body == null) {
            throw new JspTagException("'fileWrite' used without a body");
        }

        PrintWriter pw = null;
        if (fileName != null && !"log".equals(fileName)) {
            try{
                pw = new PrintWriter(new FileWriter(fileName, true));
            }
            catch (IOException e) {
                throw new JspTagException("Can not open file " + fileName +
                                          " for writing", e);
            }
        }

        ServletContext application = 
            ((PageContext) getJspContext()).getServletContext(  );
        StringWriter evalResult = new StringWriter(  );
        try {
            body.invoke(evalResult);
            if (fileName == null) {
                System.out.println(evalResult);
            }
            else if ("log".equals(fileName)) {
                application.log(evalResult.toString(  ));
            }
            else {
                pw.print(evalResult);
            }
        }
        catch (Throwable t) {
            String msg = "Exception in body of " +  this.getClass().getName(  );
            application.log(msg, t);
            throw new JspTagException(msg, t);
        }
        finally {
            if (pw != null) {
                pw.close(  );
            }
        }
    }
}

The doTag( ) method first verifies that there's a body and throws a JspTagException if not, just as in Example 21-3. It then tries to open the file for writing by creating a PrintWriter for it. If this fails, for instance because of an invalid filename or access permission problems, the IOException is caught, and a JspTagException is thrown with a message about what went wrong as well as the root cause exception.

The body fragment evaluation, plus any file manipulation operations that can go wrong, are protected by a try/catch/finally block. If an exception is thrown, a message is written to the application log file and the exception is rethrown, wrapped in a JspTagException, and the file is closed in the finally block. This way, no matter what happens, the file is always closed.

    [ Team LiB ] Previous Section Next Section