站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Servlets and JavaServer Pages: The J2EE Technology Web Tier

Servlets and JavaServer Pages: The J2EE Technology Web Tier

[ directory ]Simple JSP 2.0 Custom Tags Summary

Classic JSP Tag Handlers

Before JSP 2.0, custom tags were more complex to code and deploy. As mentioned previously in this chapter, the new SimpleTag interface can be used as a complete replacement for the older custom tag mechanism. It is recommended you use the SimpleTag interface as it is easier to use and generally much more intuitive. The following discussion on classic JSP custom tags (i.e., tags that implement the Tag interface) is solely for completeness. It can be skipped but is helpful to read because currently the majority of existing custom tags are built based on them.

A tag handler is a Java class that comprises the logic of a custom tag. There are three distinct kinds of tags: "basic" tags, "iteration" tags, and "body" tags. In the tag development model each of these tag types is represented by a Java interface: javax.servlet.jsp.Tag, javax.servlet.jsp.IterationTag, and javax.servlet.jsp.BodyTag, respectively. Each of these tag types has a well-defined set of methods and life cycle that will be fully covered by this chapter.

Basic Tags

A basic tag is a tag that implements the Tag interface. Basic tags are used to create a tag that never needs to process its body or iterate over its body content. Simple tags are the superclass of all the other custom tag interfaces and are the easiest tags to write and understand.

Tag Life Cycle

The Tag interface defines six methods all custom tags must implement. Only three of the methods are related to the tag's life cycle. The methods are doStartTag(), doEndTag(), and release(). The doStartTag() method is called when the JSP is being evaluated and the starting element of the custom tag is found. The doEndTag() method is the complement and is invoked when the ending element of the custom tag is encountered. Once the doEndTag() is finished, the release() method is invoked to clean up any resources the tag might have initialized. Figure 7-8 shows a graphical representation of this life cycle.

Figure 7-8. Tag Life Cycle

graphics/07fig08.gif


The life cycle is self-explanatory. A custom tag is encountered and the doStartTag() method is invoked. Once the end of the tag is encountered, the doEndTag() method is invoked and the tag is finished. If the tag is an empty elementfor example, <t:someTag/>, the doStartTag() and doEndTag()methods are still invoked in succession. Usually for multiple instances of the custom tag a separate tag handler instance is loaded and used, but a container is free to re-use a tag instance if the set of attributes passed to both tag elements matches. The release() method is called after the tag instance is used for the final time.

Imagine the following fictitious JSP that uses the foo tag in two different places (Figure 7-9). Each use would typically have a separate instance of the tag handler class corresponding to the foo tag. Labeled are the conceptual points where the doStartTag() and doEndTag() methods are invoked.

Figure 7-9. JSP Example of the Tag Life Cycle Invocation

graphics/07fig09.gif


Simple tags may have content in between the starting and ending tags, as shown in Figure 7-9, but the tag never gets to process its body content. This means anything that appears as the body of a simple tag cannot be processed by the tag directly, but the contents of the tag may still be processed by the container and any resulting output written to the page's output stream.

Tag Interface

Three of the methods of the Tag interface have been briefly mentioned: doStartTag(), doEndTag() and release(). However, these methods were not fully explained and there are three additional methods included in the Tag interface: getParent(), setPageContext(), and setParent(). All six of these methods are important to understand and are summarized as follows.

int doStartTag()

The doStartTag() method is invoked when the JSP encounters the starting of the custom tag. This method can be used to either initialize resources needed by the other methods of the tag, or it can perform some logic and choose to either skip evaluation of the tag's body content or skip evaluation of the rest of a JSP. The action a JSP takes after calling the doStartTag() method is based on the return value of the method. There are two valid values the doStartTag() method can return: SKIP_BODY and EVAL_BODY_INCLUDE. Both of these values are final static int primitives defined by the Tag interface. Should the doStartTag() method return the SKIP_BODY method, the JSP container will skip evaluation of the tag's body content. If the doStartTag() method returns EVAL_BODY_INCLUDE, the JSP container will evaluate the contents of the tag's body into the current JSP output buffer.

int doEndTag()

The doEndTag() method is invoked when the JSP encounters the closing element of a custom tag. This method can be used to perform any logic that needs to be done after the evaluation of the tag's body content but before the tag instance is freed for possible re-use. After calling the doEndTag() method, evaluation of the JSP continues as specified by the return value of the method. There are two valid return values for the doEndTag() method: SKIP_PAGE and EVAL_PAGE. Both of these values are final static int primitives defined by the Tag interface. Should the doEndTag() method return the SKIP_PAGE value, the current JSP will stop evaluating the current page. If the doEndTag() method returns EVAL_PAGE, the current JSP will continue to be processed.

void release()

The release() method is the final method invoked on a custom tag. The release() method is called when the custom tag is no longer going to be used by the JSP container and should release its state. The release() method is guaranteed to be invoked at least once before the tag is made available for garbage collection, but multiple doStartTag() and doEndTag() methods calls may occur before the release() method is ever called.

Tag getParent()

The getParent() method allows a custom tag to access the closest encapsulating parent custom tag. The tag is returned as the superclass Tag but can be typecast accordingly.

void setParent(Tag t)

The setParent() method is invoked by the JSP container during runtime to set the correct parent Tag object of a particular custom tag. If there is no encapsulating custom tag, null is set.

void setPageContext(PageContext p)

The setPageContext() method sets the appropriate PageContext object for a JSP. The setPageContext() method is invoked by the JSP container prior to calling the doStartTag() method.

Coding a BasicTag

For most practical purposes the doStartTag() and doEndTag() methods are all that a developer cares about when implementing a simple tag. The setPageContext() and setParent() methods are handled by the container, and the getParent() and release() methods are needed in only a few circumstances.

A simple tag can be coded in a few different ways; however, a developer is rarely required to implement the Tag interface from scratch. The JSP API includes the javax.servlet.jsp.TagSupport class, which is an adapter class that provides a default implementation of a simple tag. The easiest and most practical method of coding a simple tag is to extend TagSupport and override any method that needs customization.

Take, for example, the foo tag that has been used in all of this chapter's examples. Listing 7-26 is a simple example of extending the TagSupport class to create this previously fictitious tag.

Listing 7-26. FooTag.java
package com.jspbook;

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

public class FooTag extends TagSupport {

  public int doStartTag() throws JspException {
    JspWriter out = pageContext.getOut();
    try {
      out.println("foo");
    }
    catch (IOException e) {
      throw new JspException(e.getMessage());
    }

    return SKIP_BODY;
  }
}

Note the above tag is intended solely for illustration purposes. Listing 7-26 demonstrates a few of the subtle but important points of coding a simple tag. The first thing to notice is the declaration of the custom tag. Instead of implementing all the Tag interface methods by hand, the TagSupport class was extended.



public class FooTag extends TagSupport {

By extending TagSupport the complete Tag interface is inherited and the FooTag is a valid tag handler.

Another important part of FooTag.java to notice is that only the method needing customization is actually implemented. Out of the six required methods in the Tag interface only the doStartTag() method is overridden. The convenience of only needing to selectively customize reduces the overall amount of code and makes it easier to understand what the tag handler is doing.



  public int doStartTag() throws JspException {
    JspWriter out = pageContext.getOut();
    try {
      out.println("foo");
    }
    catch (IOException e) {
      throw new JspException(e.getMessage());
    }

    return SKIP_BODY;
  }

Inside the tag handler not much is going on. The foo tag is using the PageContext object to obtain access to the output stream and print "foo". At the end of the doStartTag() method, the SKIP_BODY value is returned, indicating that the doEndTag() method should not be invoked.

Tag Handler Exception Handling

Tag handlers deal with exceptions differently than JSP. Inside a JSP all of the scripting elements have the benefit of being surrounded by an implicit try-catch block. This often lulls a JSP developer into forgetting Java code does throw exceptions and that they must be handled. A tag handler does not have an implicit try-catch block. All exceptions must be handled explicitly during execution of the tag handler's methods.

FooTag.java, Listing 7-26, is a good example of this difference. The doStartTag() method is defined to throw a JspException, but all other forms of exceptions must be explicitly handled. In the case of a simple out.println() call, the possible java.io.IOException needs to be caught. The code does this by surrounding everything in a try-catch block and passing any exception out as a JspException. The error handling mechanism of the specific JSP is then assumed to handle the problem.

Keep in mind the issue of exception handling when coding tag handlers. This is especially an issue when porting over scripting elements into tag handler code. Often a straight conversion simply does not work because the scripting element relied on the implicit JSP try-catch statement.

TagSupport

The TagSupport class is more than a simple implementation of the Tag interface. The TagSupport object is a full implementation of both the Tag and IterationTag interfaces along with some other miscellaneous helpful methods. Not all of the methods are worth mentioning right now, but there is one important one to be aware of:



static Tag findAncestorWithClass(Tag from, java.lang.Class klass)

The findAncestorWithClass() method is a superior form of the getParent() method provided by the Tag interface. If a custom tag relies on cooperating with another custom tag, it needs to be able to easily find the other tag. Calling the getParent() method does return the nearest neighboring custom tag, but it might not be the one the tag is expecting to cooperate with. In lieu of always needing to loop through and verify parent class types, the findAncestorWithClass() method can be used.

The TagSupport helper class is further covered later in the chapter with iteration tags.

Using a Tag Handler and TLD with JSP

So far we have discussed how to build all of the components required for a simple tag but have yet to actually put everything together. It is a helpful exercise to walk through this process and demonstrate a working example of a custom tag built from scratch. So far the only tag we have built in this chapter is the rather unhelpful foo tag. If you recall, this tag already has an appropriate TLD entry and is ready to be used. To complement this tag, let us build another slightly more helpful basic tag.

The new tag we are going to build is the date tag. The date tag is another basic tag handler, and it will be designed to give the current date and time of the server the JSP was executed from. Save Listing 7-27 as DateTag.java.

Listing 7-27. DateTag.java
package com.jspbook;

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

public class DateTag extends TagSupport {

  public int doStartTag() throws JspException {
    JspWriter out = pageContext.getOut();
    try {
      Date date = new Date();
      out.println(date.toString());
    }
    catch (IOException e) {
      throw new JspException(e.getMessage());
    }

    return SKIP_BODY;
  }
}

The preceding code greatly resembles FooTag.java. It is a simple tag handler that extends TagSupport and overrides the doStartTag() method. The only difference between the two tags is that DateTag.java creates a new java.util.Date object and serializes it for a user to see.

The primary reason for creating the date tag was so a new tag would need to be added to the example TLD file. This is something that needs to be done each time a tag is added to a tag library so it is helpful to be familiar with the process. You'll recall that the example.tld file was previously created and saved in the /WEB-INF directory of the jspbook Web Application. Edit this file to now include an entry for the newly created date tag as shown in Listing 7-28.

Listing 7-28. DateTag.java Addition to examples.tld
<tag>
  <name>date</name>
  <tag-class>com.jspbook.DateTag</tag-class>
  <body-content>empty</body-content>
</tag>

Make sure the addition appears as a child of the taglib element and either immediately before or after an instance of a tag element. Compile both the FooTag.java and DateTag.java files and reload the jspbook Web Application to reflect the changes.

With both tag handler codes complete and the TLD up to date, the tag library is ready for use in a JSP. Save Listing 7-29 as TagLibraryExample.jsp in the root directory of the jspbook Web Application.

Listing 7-29. TagLibraryExample.jsp
<%@ taglib prefix="x" uri="http://www.jspbook.com/example" %>
<html>
  <head>
    <title>A Custom Tag Example</title>
  </head>
  <body>
    The date/time is <x:date/>, <x:foo/>!
  </body>
</html>

Browse to http://www.jspbook.com/TagLibraryExample.jsp to see the results of the custom tags. Figure 7-10 shows the output rendered by a Web browser. Both the date and foo tags are appropriately executed and appear to the client as some text in an HTML page.

Figure 7-10. Browser Rendering of TagLibraryExample.jsp

graphics/07fig10.gif


The page displayed in Figure 7-10 is the conclusion of this simple walk-though implementation of custom tags. You now know how to create a simple tag, define it in a TLD, and create a JSP that uses the tag. This is a good start to understanding custom tags, but we have yet to see many of the features that make custom tags powerful.

Re-Using Tags

An important and commonly misunderstood point is that a container has the option to cache a tag instance instead of creating a new tag each time a JSP uses a custom tag. This allows containers to create one instance of a tag and re-use it for efficiency reasons when needed. However, there are limitations on the tags that can be re-used. When a tag is first used, it will be initialized by the container. All the properties set in the tag element are passed to the tag; if the tag has optional properties and those properties are not defined on the page, the tag will use default values for those elements. Re-using a tag implies that the container will not need to re-initialize the tag; this has two consequences. First, a tag can only be re-used if it has exactly the same set of attributes as a previous usage. Second, it is vital that a tag instance re-initialize any properties whose values have changed to the state they were in just before doStartTag() method was called. In many cases this second criterion is problematic because a container relies on the tag resetting itself when the doEndTag() method is invoked. When coding a custom tag that initializes class-wide variables, be sure these variables are reset each time the doEndTag() method is invoked.

A simple example of this is FormattedDateTag.java, Listing 7-30. FormattedDateTag.java declares the class-wide variable format. After running through the tag once, the variable might be altered. To prevent this alteration from lingering around if the tag is cached, the doStartTag() method should be changed to reset format to null. The problem was purposely overlooked in previous examples to keep things simple and because it is not really a severe problem. However, to always ensure FormattedDateTag.java performs as expected, the following addition needs to be made to the code.

Listing 7-30. FormattedDateTag.java Fixed for Consistency
package com.jspbook;

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

public class FormattedDateTag extends TagSupport {
  private String format;

  public void setFormat(String format) {
    this.format = format;
  }

  public int doStartTag() throws JspException {
    JspWriter out = pageContext.getOut();
    try {
      if (format != null) {
        SimpleDateFormat sdf =
          new SimpleDateFormat(format);
        out.println(sdf.format(new Date()));
      }
      else {
        out.println(new Date().toString());
      }
    }
    catch (IOException e) {
      throw new JspException(e.getMessage());
    }
    finally {
      format = null;
    }

    return SKIP_BODY;
  }
}

TryCatchFinally Interface

A tag's life cycle is fully defined by the JSP specification. As stated previously, the doStartTag() method is invoked after the tag is fully initialized; between the doStartTag() and doEndTag() method calls, the container assumes that the tag is holding state that must be preserved. After the doEndTag() method, the tag is available for re-use. The release() method is called before the tag is made available for garbage collection. After calling the release() method, the container assumes that the tag has given up all resources including properties, parent references, and so on.

However, it is possible that the doEndTag() method is never called. This happens if the tag throws an exception before the doEndTag() would normally be called. If the tag creates or manages resources and would normally give up those resources in its doEndTag() method, the tag now has a problem! How does the tag release those resources? To guarantee that resources are released even in the case of failure, the tag should implement the TryCatchFinally interface. TryCatchFinally is an interface representation of the try-catch-finally block shown in Chapter 4 and has two methods: doCatch() and doFinally(). These two methods provide identical functionality to the catch and finally clauses available for try statements. An easy way to understand the concept is to assume a custom tag implementing the TryCatchFinally interface always gets translated to a Servlet with the equivalent of the following code:



Tag tag = getTagFromSomewhere();
// initialize tag
// ...
// use tag
try {
   tag.doStartTag();
   ..
   tag.doEndTag();
}
catch (Throwable t)
{
   // catch exception
   tag.doCatch(t);
}
finally
{

   // release usage specific resources
   tag.doFinally();
}

tag.release(); // release long-term resources

The preceding code illustrates how a container can use the TryCatchFinally interface to provide the equivalent of a try-catch-finally block for a custom tag. Understanding this concept is critical as it is the only way to mimic a try-catch-finally statement that spans across life cycle methods of a custom tag based on the Tag interface. It is the responsibility of the tag author to correctly declare and implement the TryCatchFinally interface methods in a custom tag.

Cooperating Tags

Among the cardinal rules of programming are keeping your methods short and breaking complex code into reusable components. Supertags should never be written that try to do everything. Each tag should be a self-contained unit that does a single part of a goal. This often means that tags need to cooperate with other tags if they are to work well.

Tags typically cooperate by nesting one tag inside another.



<%@taglib prefix="mt" uri="/WEB-INF/mytags.tld" %>
<mt:outerTag>
  <mt:innerTag/>
</mt:outerTag>

To cooperate, the innerTag needs to get a reference to the outerTag. As we saw earlier, when tags are initialized, the container calls the tag's setParent() method, passing a reference to the Java object that is the parent. For the outer tag shown in the previous example, the parent would be passed as null. However, the inner tag shown previously would be passed a reference to the Java class that implements the outerTag tag. The reference passed is of type Tag. The inner tag can then call methods on or get data from the parent. To do this, the inner tag would simply cast the reference to the correct type, as shown in Listing 7-31.

Listing 7-31. Fictitious Cooperating Tag Handler
public class InnerTag extends TagSupport {
   OuterTag outerTag = null;

   public int doStartTag() throws JspException {
      outerTag = (OuterTag)parent;
      // invoke any needed methods
      outerTag.fooMethod();
      // do other work here
   }
}

Notice that while the inner tag can get a reference to its parent, there is no standard way for the references to be passed the other way. A parent does not get a reference to its children.

In Listing 7-31 the inner tag's doStartTag() method defines a variable of type OuterTag that is assumed to be the tag handler class of the outerTag tag. To use a reference to the outer tag, the inner tag simply casts the parent data member to be an OuterTag. But where does the tag get the parent data member from? It's not defined in the tag. Remember that the tag extends TagSupport and that TagSupport implements Tag, which means that TagSupport provides an implementation of the setParent() method. This implementation simply defines a variable



Tag parent;

and saves the reference passed to it, the setParent call:



public void setParent(Tag t) {
   parent = t;
}

While in simple cases Listing 7-31 works perfectly fine, there is a flaw with the code. The child is blindly casting the parent value to the type it should be if one were to assume innerTag was always nested directly one level deep from a outerTag. But suppose that the tag is nested not one but several levels deep. The following is a perfectly valid JSP:



<%@taglib prefix="mt" uri="/WEB-INF/mytags.tld" %>
<%@taglib prefix="yt" uri="/WEB-INF/yourtags.tld" %>
<mt:outerTag>
  <yt:someOtherInnerTag>
    <mt:innerTag/>
  </yt:someOtherInnerTag>
</mt:outerTag>

Note that the tag is not only one extra level detached from its parent; its parent is also from a different tag library. Now the inner tag may not know anything about its parent. Not only that, for the inner tag to work it has to get a reference to the outerTag, not the random tag stuck in between.

Using only the Tag interface to solve this problem is reasonably difficult and requires looping through parent tags and checking each tag handler's type. An alternative is the convenience findAncestorWithClass() method provided by TagSupport. Recall TagSupport provides the following method:



static Tag findAncestorWithClass(Tag from, java.lang.Class klass)

This method finds a reference to the tag implemented by a given Java class as specified by the klass parameter. The container maintains a stack of tags linked by their parent values. Starting at the from parameter, this method walks that stack looking for an instance of the right Class. Using what was just explained Listing 7-31 would be better coded as shown by Listing 7-32.

Listing 7-32. Better Implementation of Cooperating Tags
public class InnerTag extends TagSupport {
  OuterTag outerTag = null;

  public int doStartTag() throws JspException {
    outerTag = (OuterTag)findAncestorWithClass(this, OuterTag.class);
    if(outerTag != null) {
      // invoke needed methods
      outerTag.callParentMethod();
      // do other work here
    }
  }
}

This code works independent of tags nested arbitrarily deep and cases where tags from different tag libraries are mixed.

Mixing New Tags with Classic Tags

Cooperation among classic tags is reasonably simple as all classic tags implement the Tag interface. This means that getParent, setParent, and findAncestorWith Class can all rely on the fact that the tags they are dealing with are all instances of javax.servlet.jsp.tagext.Tag. Cooperation among simple tags is also easy; all simple tags implement the same interface, javax.servlet.jsp.tagext.SimpleTag. Problems arise when simple tags and classic tags are mixed. Imagine this scenario:



<mt:simpleTag>
    <mt:classicTag/>

</mt:simpleTag>

Here, there is a problem. When the container calls the classicTag's setParent, it wants to pass a reference to a tag, but the simple tag does not implement Tag. To get around this issue, the container instead creates an instance of a TagAdapter. The TagAdapter simply wraps a simple tag and exposes it as if it were a tag. The nested classicTag could now be coded like this:



public class InnerTag extends TagSupport {
   OuterTag outerTag = null;

   public int doStartTag() throws JspException {
      outerTag = (OuterTag)parent.getAdaptee();
      // invoke any needed methods
      outerTag.fooMethod();
      // do other work here
   }
}

Notice that the tag now has to call getAdaptee(); this method gets a reference to the tag that the adapter is wrapping.

This problem does not arise if the tags were nested like this:



<mt:classicTag>
    <mt:simpleTag/>

</mt:classicTag>

The simple tag's setParent() method takes a reference to a JspTag interface. This is a new interface introduced in JSP 2.0 that has no methods. It is simply there to provide a degree of type safety and to support situations such as this. In JSP 2.0 the Tag interface extends JspTag, so all classic tags extend the JspTag interface automatically. The SimpleTag interface also extends Tag so all simple tags are also instances of JspTag.

There is still one problem left to solve. We said previously that simply calling getParent() wasn't good enough; a nested tag should really call the findAncestorWithClass() method. The findAncestorWithClass() method works fine if a classic tag needs to find a reference to another classic tag it is nested inside, but if a classic tag needs to get a reference to an arbitrary simple tag somewhere within the stack of tags, the findAncestorWithClass() method will not work because it is typed to return an instance of Tag and not an instance of JspTag. To fix this problem, SimpleTagSupport has its own copy of findAncestorWithClass that works correctly.

Given this



<mt:simpleTag>
  <yt:someOtherInnerTag>
    <mt:innerTag/>
  </yt:someOtherInnerTag>
</mt:simpleTag>

the inner tag should be coded like this:



public class InnerTag extends TagSupport {
  MySimpleTag simpleTag = null;

  public int doStartTag() throws JspException {
    simpleTag = ( MySimpleTag )SimpleTagSupport.findAncestorWithClass(this, OuterTag.class);
    if(simpleTag != null) {
      // invoke needed methods
      simpleTag.callParentMethod();
      // do other work here
    }
  }
}

So, if a nested tag is looking for a classic tag ancestor, it should use the TagSupport.findAncestorWithClass() method. However, if it's looking for a simple tag ancestor, it should use the SimpleTagSupport.findAncestorWithClass() method.

Iteration Tags

Iteration tags are an enhanced version of a simple tag. Iteration tags can do everything a simple tag can with the addition of being able to repeatedly evaluate its body. The reevaluation is managed by introducing a new method in the custom tag life cycle: doAfterBody(). The doAfterBody() method is invoked after the doStartTag() method and before the doEndTag() method. The doAfterBody() method determines if either the body should be reevaluated, calling doAfterBody() again, or if the doEndTag() method should be invoked.

With the doEndTag() method added to the simple tag life cycle, the iteration tag life cycle now appears in Figure 7-11.

Figure 7-11. Iteration Tag Life Cycle

graphics/07fig11.gif


IterationTag Interface

For a tag handler to be treated as an iteration tag, it must implement the javax.servlet.jsp.IterationTag interface. The IterationTag interface is a subclass of the Tag interface and defines only one new method:



public int doAfterBody() throws JspException

The doAfterBody() method is invoked after the doStartTag() method if it returns EVAL_BODY_INCLUDE. As illustrated in Figure 7-11 the doAfterBody() method can be invoked multiple times depending on the return value of the method. If doAfterBody() returns EVAL_BODY_AGAIN, then the tag's body is reevaluated and the doAfterBody() method is invoked again. If the doAfterBody() method returns SKIP_BODY, then the doEndTag() method is invoked.

While not re-listed, keep in mind all of the simple tag methods are still available to an iteration tag. Converting a simple tag to an iteration tag is as easy as implementing the IterationTag interface and coding a doAfterBody() method for the tag.

Coding an Iteration Tag

Iteration tags are as easy to code as simple tags. The helper class TagSupport implements both the Tag and IterationTag interfaces. For practical purposes extending TagSupport for both simple and iteration tags is of no consequence to a JSP developer. The single method the IterationTag interface adds does not interfere with the execution of a simple tag, nor does it add significant overhead to the code. The real difference between coding a simple tag and iteration tag is choosing when you need to have the functionality of the doAfterBody() method. If the doAfterBody() method is needed, simply code it into a tag handler and have the doStartTag() method return EVAL_BODY_INCLUDE.

The most helpful use of iteration tags is implied by their name, iteration. By using an iteration tag, redundant chunks of template text can be eliminated in a JSP. The functionality is identical to that of a scriptlet using a for, while, or do while loop. Consider the JSP in Listing 7-33.

Listing 7-33. A JSP with a Few Links
<%
  // An array of links
  String[] links = new String[5];
  links[0] = "http://www.jspbook.com";
  links[1] = "http://java.sun.com";
  links[2] = "http://www.jspinsider.com";
  links[3] = "http://www.developmentor.com";
  links[4] = "http://www.aw.com";
%>
<html>
  <head>
    <title>Eliminating Redundency</title>
  </head>
  <body>
    Some helpful JSP and Servlet links
    <ul>
      <li><a href="<%= links[0] %>"><%= links[0] %>
          </a></li>
      <li><a href="<%= links[1] %>"><%= links[1] %>
          </a></li>
      <li><a href="<%= links[2] %>"><%= links[2] %>
          </a></li>
      <li><a href="<%= links[3] %>"><%= links[3] %>
          </a></li>
      <li><a href="<%= links[4] %>"><%= links[4] %>
          </a></li>
    </ul>
  </body>
</html>

Listing 7-33 is illustrating a poor use of JSP. One benefit of the page compared to an HTML equivalent is that it is re-using the array values. This simple re-use allows for each single change to an array value to be reflected in the two later uses on JSP. However, this JSP still contains a lot of redundant markup. Each line of code between the unordered list tag, <ul>, is exactly the same as the line above it with the exception of the array slot being accessed. These lines of code should be eliminated in favor of one line of code that is repeatedly used while iterating through the values in the array. This can easily be done with a scriptlet and either a for, while, or do while loop, as illustrated in Chapter 3, but it can also be done by an iteration tag.

Right now it is up to you to eliminate the markup redundancy using what has been explained about iteration tags. This can be done with the iteration tag in Listing 7-34.

Listing 7-34. LinkIterationTag.java
package com.jspbook;

import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import javax.servlet.ServletRequest;

public class LinkIterationTag extends TagSupport {
  String[] links;
  int count;

  public int doStartTag() throws JspException {
    // get the array of links
    ServletRequest request = pageContext.getRequest();
    links = (String[])request.getAttribute("links");
    // reset count
    count = 0;

    // set current link
    pageContext.setAttribute("link", links[count]);
    count++;
    return EVAL_BODY_INCLUDE;
  }

  public int doAfterBody() throws JspException {
    if(count < links.length) {
      try {
        pageContext.setAttribute("link", links[count]);
        count++;
      }
      catch (Exception e) {
        throw new JspException();
      }
      return EVAL_BODY_AGAIN;
    }
    else {
      return SKIP_BODY;
    }
  }
}

In the preceding code a simple iteration tag is created that relies on a variable bound to request scope named links. The tag takes this variable, treats it as an array of strings, and loops through each object in the array. The looping is done by setting a PageContext variable, link, and evaluating the tag's body. Before using this tag we need to add an appropriate entry in this chapter's example TLD file. Edit example.tld to include the tag definition in Listing 7-35.

Listing 7-35. LinkIterationTag.java Entry in example.tld
  <tag>
    <name>iterateLinks</name>
    <tag-class>com.jspbook.LinkIterationTag</tag-class>
    <body-content>JSP</body-content>
  </tag>

Listing 7-36 provides an example of how a JSP might use this tag.

Listing 7-36. LinkIterationTag.jsp
<%
  // An array of links
  String[] links = new String[5];
  links[0] = "http://www.jspbook.com";
  links[1] = "http://java.sun.com";
  links[2] = "http://www.jspinsider.com";
  links[3] = "http://www.developmentor.com";
  links[4] = "http://www.aw.com";
  request.setAttribute("links", links);
%>
<%@ taglib prefix="x" uri="http://www.jspbook.com/example" %>
<html>
  <head>
    <title>Eliminating Redundency</title>
  </head>
  <body>
    Some helpful JSP and Servlet links
    <ul>
    <x:iterateLinks>
      <li><a href="${link}">${link}</a></li>
    </x:iterateLinks>
    </ul>
  </body>
</html>

Notice the preceding code now eliminates the redundant markup in the unordered list. Now one set of the markup is used with the link values being placed dynamically during each body evaluation of the iterateLinks tag.

Save LinkIterationTag.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application and save LinkIterationTag.jsp in the root directory of the jspbook Web Application. After compiling the tag handler, reload the Web Application and visit http://127.0.0.1/jspbook/LinkIterationTag.jsp. The new tag handler loops through all of the objects in the array and displays them with the expected markup. Figure 7-12 shows a browser rendering of the output.

Figure 7-12. Browser Rendering of LinkIterationTag.jsp

graphics/07fig12.gif


In addition to coding and seeing an iteration tag work, there are two more points of interest in Listing 7-36; notice that the array of String objects, links, is passed to the custom tag via request scope. However, the link variable is passed from the tag back to the JSP in page scope. Why isn't the page scope used in both of these cases? The code is done in consideration of a practical application of this type of code. The preceding JSP can actually be split into two parts: one page that creates the links array and places it in request scope and a second page that contains only the HTML markup and the iterateLinks tag. This type of design would cleanly separate the two parts of the page and allow for the second half to be used with any array of links. A degree of separation such as this is very helpful and it is introduced now so that you can start thinking about it before reaching the full discussion of design patterns in Chapter 11. The second point of interest is the use of the PageContext getAttribute() method for accessing the appropriate link variable during each evaluation of the iterateLinks tag body. For cleaner code this would best be done by either another custom tag or by setting a JavaBean in page scope and using the standard getProperty action. In the given example it is used for simplicity.

Body Tags

In this chapter we have so far seen two of the three types of tag handlers: simple tags and iteration tags. The third type of tag handler is a body tag. Body tags extend the functionality of an iteration and allow a tag handler to access and manipulate the content contained within its body. Because of this increased functionality-body tags require a more complex life cycle and are more processor, intensive than simple tags and iteration tags.

The body tag life cycle extends the iteration tag life cycle by adding in one new method: doInitBody(). The doInitBody() method is invoked after the doStartTag() method but before the first evaluation of the tag's body content. Figure 7-13 illustrates the body tag life cycle.

Figure 7-13. Body Tag Life Cycle

graphics/07fig13.gif


BodyTag Interface

A body tag must implement the BodyTag interface. The BodyTag interface defines the following methods:

Coding a Body Tag

The TagSupport object previously used when coding simple and iteration tags does not implement the BodyTag interface. The BodyTag interface adds a noticeable amount of overhead in comparison to a basic simple or iteration tag. Managing a BodyContent object is something that a tag does not require unless it wishes to access its body. For this reason the JSP API provides a separate extension of the TagSupport object for authoring a body tag, BodyTagSupport. The BodyTagSupport object is a subclass of TagSupport and provides all of the methods required by the Tag, IterationTag, and BodyTag interface.

In addition to all the methods of the BodyTag interface, BodyTagSupport provides two extra methods:

Similar to other tags, body tags can have two types of content in their body: static text or JSP. However, with body tags it is a slightly more important issue to have the correct type of body content for the tag. Since a body tag has access to its body, there is the possibility that the tag might be expecting to interpret the content verbatim, not after the JSP container evaluates it. This is especially important in cases where the body tag might be being used to interpret a JSP-like code that would be munged by the JSP container.

Body tags are rarely used in practice. The functionality they provide is helpful, but it is not functionality that tends to follow good coding practices popular design patterns use. There are a few use cases, some of which are implemented by the JSTL, where a body tag is being used to evaluate code contained within its body. Specifically, it is not uncommon to see a set of custom tags that can process an embedded chunk of XML or SQL. In these cases the tags exist but are rarely used because a different design that does not rely on the tags can better implement the same functionality.

Nested Body Tag Ambiguities

Body tags introduce a few ambiguities with what we understand so far about JSP. The problem arises because of the BodyContent buffer a body tag uses to handle content. A BodyContent object acts as the JspWriter for content in a body tag's content. What happens when a nested tag calls the pageContext getOut() method? The getOut() method returns the JspWriter object stored by the PageContext object representing the JSP. This is normally the JspWriter object, which is associated with sending output to a buffer that eventually flushes directly to a client. However, calling this method directly from a tag embedded within a body tag intuitively seems to break the system. Content of a body tag should be sent to the BodyContent of the appropriate body tag. It should not sidestep this process and directly access the main JSP's output stream. An important point to understand is that nested tags in this scenario do not break the system. When a body tag obtains a BodyContent object, it does it by calling the pushBody() method of PageContext. This method saves the existing JspWriter and creates a new one that is then associated with the JSP's PageContext object as the current output stream. The end result is that nested tags within a body tag always receive the JspWriter associated with the parent body tag and therefore work as expected.

Tag Scripting Variables

A custom tag has the ability to define scripting variables for use with the scripting elements. This functionality is not commonly used because it tends to go against commonly agreed-upon, good JSP design patterns. Custom tags are best used to eliminate the amount of scripting elements that appear on a JSP. Using a custom tag to create a few scripting variables only encourages more scripting elements to be used. This is a bad practice and leads to unmaintainable code. For this reason, in general, this entire section is not recommended for use. It is included solely for completeness and so that you as a reader can identify code that attempts to use the functionality.

There are two methods by which a JSP developer can declare scripting variables via a custom tag. The first solution is by means of a Tag Extra Information (TEI) class. Originally this class was designed as the only method to declare JSP scripting variables and validate tag attribute values at translation time. TEI classes were superceded in JSP 1.2 by the combination of TLD elements to declare scripting variables and TagLibraryValidator (TLV) classes. These two methods are covered in the following sections, and TagLibraryValidator classes are covered later in the chapter.

TLD-Declared Scripting Variables

Scripting variables can be declared by the TLD by using the variable child element of the tag element. The variable element in turn has a few children elements that describe the scripting variable that is being declared. The children elements are as follows:

To declare a simple scripting variable, only the name-given attribute is required. Take, for example, the example for an iteration tag, Listing 7-34. For simplicity this code used the PageContext getAttribute() method via a scriptlet to access an object passed from a tag handler. The best solution would be to abstract access to the attribute behind another custom tag, but another fairly good solution is to have the tag handler declare a few scripting variables. This can easily be done by adding the change to the tag's TLD entry, as shown in Listing 7-37.

Listing 7-37. Declaring Scripting Variables for the iterateLinks Tag
<tag>
  <name>iterateLinks</name>
  <tag-class>com.jspbook.LinkIterationTag</tag-class>
  <body-content>JSP</body-content>
  <variable>
    <name-given>link</name-given>
    <variable-class>String</variable-class>
  </variable>
</tag>

Edit example.tld to include the preceding declaration. The newly declared variable will then be available for a JSP using the iterateLinks tag. Listing 7-38 modifies LinkIterationTag.jsp to show how the scripting variable can be used.

Listing 7-38. LinkIterationTagVariable.jsp
<%
  // An array of links
  String[] links = new String[5];
  links[0] = "http://www.jspbook.com";
  links[1] = "http://java.sun.com";
  links[2] = "http://www.jspinsider.com";
  links[3] = "http://www.developmentor.com";
  links[4] = "http://www.aw.com";
  request.setAttribute("links", links);
%>
<%@ taglib prefix="x" uri="http://www.jspbook.com/example" %>
<html>
  <head>
    <title>Eliminating Redundency</title>
  </head>
  <body>
    Some helpful JSP and Servlet links
    <ul>
    <x:iterateLinks>
      <li><a href="${link}">${link}</a></li>
    </x:iterateLinks>
    </ul>
  </body>
</html>

Notice the only difference between the preceding code and the code for LinkIterationTag.jsp is the preceding code can directly reference the link variable instead of using PageContext getAttribute(). Since the TLD file declares the link scripting variable, it is valid to use a direct reference to the link variable, even though it was not declared previously by a scriptlet.

TagExtraInfo Classes

TagExtraInfo classes are the original method for declaring JSP scripting variables from a tag handler. A TEI class provides the same information as the TLD variable element but in a slightly different method. Unlike the TLD variable element, a TEI class declares scripting elements from inside a Java class. The TLD is used only to reference the appropriate TEI class.

When creating a TEI class, two things are required: a javax.servlet.jsp.TagExtraInfo subclass and a teiclass element in the TLD declaration for the tag. The TagExtraInfo subclass is responsible for declaring any scripting variables the tag creates, and the teiclass element provides the name of this class. The TEI class itself is responsible for providing a getVariableInfo() method that returns an array of javax.servlet.jsp.tagext.VariableInfo objects describing all of the scripting variables.

VariableInfo objects are just a Java object representation of the information needed for a scripting element: the name for the script variable, the type of the variable, its availability scope, and whether the page needs to declare the variable. These values correspond to the name-given or name-from-attribute, variable-type, scope, and declare elements, respectively, of the TLD variable element. In code, all of these values are passed via the VariableInfo object construct that is illustrated in Listing 7-39.

Listing 7-39. TEI Class for LinkIterationTag.java
import javax.servlet.jsp.tagext.*;

public class LinkIterationTEI extends TagExtraInfo {
  public VariableInfo[] getVariableInfo(TagData data) {
    // create a VariableInfo
    VariableInfo info =
      new VariableInfo("link", "String", true,

                       VariableInfo.NESTED );
    VariableInfo[] vi = { info };
    return vi;
  }
}

The preceding code is the equivalent to the previous TLD entry for the iterateLinks tag, Listing 7-35. If you wish to try the TEI class, save the above code as LinkIterationTEI.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web application. Compile LinkIteration.java and add Listing 7-40 in example.tld.

Listing 7-40. TEI TLD Entry
  <tag>
    <name>iterateLinks</name>
    <tag-class>com.jspbook.LinkIterationTag</tag-class>
    <teiclass>com.jspbook.LinkIterationTEI</teiclass>
    <body-content>JSP</body-content>
<!--
    <variable>
      <name-given>link</name-given>
      <variable-class>String</variable-class>
    </variable>
-->
  </tag>
...

After adding the preceding entry in example.tld, reload the jspbook Web Application and visit http://127.0.0.1/jspbook/LinkIterationTagVariable.jsp. The same results are displayed as in Figure 7-12, but this time the JSP is using the TEI class instead of the TLD entry to declare the scripting variables.

Tag Library Listeners

A custom tag library may rely on the functionality provided by a Servlet event listener, but we know from Chapter 2 that listener objects need to be registered with web.xml. To solve this problem, JSP custom tag developers may deploy listeners via the Tag Library Descriptor.




<!ELEMENT taglib (tlib-version, jsp-version, short-name, uri?, display-name?, small-icon?,
graphics/ccc.gif large-icon?, description?, validator?, listener*, tag+) >

And a listener element is as follows:



<listener>some.package.MyListener</listener>

The order in which listeners defined as tags are called is undefined; it is commonly the order in which the container discovers the TLD files.

Validation

Tags may be authored and used by different people. It can be very difficult for the tag author to tell the tag user exactly how to use the tag. For example, a tag's attributes may have to be a specific type or have a specific value range, a given tag may need a certain type of parent, or tags may expect to have specific children.

While the custom action mechanism of JSP has some built-in validation, this is limited in what it can achieve. For example, the page compiler can check that all the "required" attributes are defined and that the user hasn't set any attribute the tag is not expecting. However, the compiler cannot check that the attribute values are of the right type (perhaps the whole page is text) or that the values are in the correct range. This can only be done by the tag author. Since the JSP 1.2 release, a tag author can specify that he or she wants to validate a page. This is done by creating a class that implements javax.servlet.jsp.tagext.TagLibraryValidator and specifying this class's availability through the TLD. This class gives the author a great deal of flexibility during the validation process.

The TagLibraryValidator class looks like this:



java.util.Map getInitParameters()
void release()
void setInitParameters(java.util.Map map)
ValidationMessage[] validate(String prefix,
                             String uri,
                             PageData page)

To associate the validator with a tag, an entry is added to the TLD:




<!ELEMENT taglib (tlib-version, jsp-version, short-name, uri?, display-name?, small-icon?,
graphics/ccc.gif large-icon?, description?, validator?, listener*, tag+) >

Notice that a validator is associated with a tag library, not with individual tags within the library. The DTD for the validator element looks like this:



<!ELEMENT validator (validator-class, init-param*, description?) >

and for the init-param like this:



<!ELEMENT init-param (param-name, param-value, description?)>

An example TLD would look something like:



<taglib>
    <!-- other taglib entries -->
    <validator>
        <validator-class>some.package.TagValidator</validator-class>
        <init-param>
            <param-name>count</param-name>
            <param-value>1</param-value>
        </init-param>
        <init-param>
            <param-name>language</param-name>
            <param-value>en</param-value>
        </init-param>
    </validator>
<!-- other taglib entries -->
</taglib>

The validator is executed once and only once, when the page is first compiled, and validators have a life cycle. The first thing the page compiler does is call the setInitParameters() method passing a java.util.Map containing the init-params. Once initialized the page compiler calls the validate method. Like tags, validators can be cached for re-use. If the validator is to be re-used, the page compiler first determines if the set of init-params are the same as the previous use; if they are, then validate is called again. If the init-params have changed, the page compiler calls release and then calls the setInitParameters() method, passing the new set, and finally calls validate.

The validate method is passed the prefix the tag is using, the URI from the taglib directive, and a PageData object. It is the PageData object that gives the validator access to the page. The PageData object wraps an InputStream through which the validator can read the page in XML format. This view of the page is the JSP 1.2 XML view. The page starts with a <jsp:root...> declaration and contains all the page data as XML elements.

The validate method will parse the page and check its validation criteria. If the page is valid, the method returns null. If the validation fails, the page returns an array of ValidationMessage objects. Each ValidationMessage object should contain a validation message, and an id identifying the element that caused the error. In JSP 1.2 a container when generating an XML view of a page is able to add a jsp:id attribute to each element on the page (this is entirely optional). This attribute extends the attribute set the element normally has and is available to the validator class through the PageData object's XML view. If this value is there, the validator should use this when reporting validation errors.

[ directory ]Simple JSP 2.0 Custom Tags Summary