Chapter 10. Building a Custom Tag Library

Describe the semantics of the "Classic" custom tag event model when each event method (doStartTag, doAfterBody, and doEndTag) is executed, and explain what the return value for each event method means; and write a tag handler class.

The classes and interfaces used to implement classic tag handlers are contained in the javax.servlet.jsp.tagext package. Classic tag handlers implement either the Tag, IterationTag, or BodyTag interface. Interfaces can be used to take an existing Java object and make it a tag handler. For newly created classic tag handlers, you can use the TagSupport and BodyTagSupport classes as base classes. These classes and interfaces are contained in the javax.servlet.jsp.tagext package.

Tag handler methods defined by the Tag and BodyTag interfaces are called by the JSP page's servlet at various points during the evaluation of the tag. When the start element of a custom tag is encountered, the JSP page's servlet calls methods to initialize the appropriate handler and then invokes the handler's doStartTag method. When the end element of a custom tag is encountered, the handler's doEndTag method is invoked for all but simple tags. Additional methods are invoked in between when a tag handler needs to manipulate the body of the tag.

Table 10.1. Tag Handler Methods

Tag TypeInterfaceMethods
BasicTagdoStartTag, doEndTag
AttributesTagdoStartTag, doEndTag, setAttribute1, ..., setAttributeN , release
BodyTagdoStartTag, doEndTag, release
Body, iterative evaluationIterationTagdoStartTag, doAfterBody, doEndTag, release
Body, manipulationBodyTagdoStartTag, doInitBody, doAfterBody, doEndTag, release

A tag handler has access to an API that allows it to communicate with the JSP page. The entry points to the API are two objects: the JSP context (javax.servlet.jsp.JspContext) for simple tag handlers and the page context (javax.servlet.jsp.PageContext) for classic tag handlers. JspContext provides access to implicit objects. PageContext extends JspContext with HTTP-specific behavior. A tag handler can retrieve all the other implicit objects (request, session, and application) accessible from a JSP page through these objects. In addition, implicit objects can have named attributes associated with them. Such attributes are accessed using [set|get]Attribute methods.

The Tag interface defines the basic protocol between a tag handler and a JSP page's servlet. It defines the life cycle and the methods to be invoked when the start and end tags are encountered.

The JSP page's servlet invokes the setPageContext, setParent, and attribute setting methods before calling doStartTag. The JSP page's servlet also guarantees that release will be invoked on the tag handler before the end of the page.

Here is a typical tag handler method invocation sequence:

ATag t = new ATag();
t.setPageContext(...);
t.setParent(...);
t.setAttribute1(value1);
t.setAttribute2(value2);
t.doStartTag();
t.doEndTag();
t.release(); 
					

The BodyTag interface extends IterationTag by defining additional methods that let a tag handler manipulate the content of evaluating its body:

  • setBodyContent - Creates body content and adds to the tag handler

  • doInitBody - Called before evaluation of the tag body. This method will not be invoked for empty tags or for non-empty tags whose doStartTag() method returns SKIP_BODY or EVAL_BODY_INCLUDE.

A typical invocation sequence is:

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

					

If the tag handler does not need to manipulate the body, the tag handler should implement the Tag interface. If the tag handler implements the Tag interface and the body of the tag needs to be evaluated, the doStartTag method needs to return Tag.EVAL_BODY_INCLUDE; otherwise it should return Tag.SKIP_BODY.

If a tag handler needs to iteratively evaluate the body, it should implement the IterationTag interface. The tag handler should return IterationTag.EVAL_BODY_AGAIN from IterationTag.doAfterBody method if it determines that the body needs to be evaluated again.

If the tag handler needs to manipulate the body, the tag handler must implement BodyTag (or be derived from BodyTagSupport).

When a tag handler implements the BodyTag interface, it must implement the doInitBody and the IterationTag.doAfterBody methods. These methods manipulate body content passed to the tag handler by the JSP page's servlet.

Body content supports several methods to read and write its contents. A tag handler can use the body content's getString or getReader methods to extract information from the body, and the writeOut(out) method to write the body contents to an out stream. The writer supplied to the writeOut method is obtained using the tag handler's getPreviousOut method. This method is used to ensure that a tag handler's results are available to an enclosing tag handler.

If the body of the tag needs to be evaluated, the doStartTag method needs to return BodyTag.EVAL_BODY_BUFFERED; otherwise, it should return Tag.SKIP_BODY.

The doInitBody method is called after the body content is set but before it is evaluated. You generally use this method to perform any initialization that depends on the body content.

The IterationTag.doAfterBody method is called AFTER the body content is evaluated. IterationTag.doAfterBody must return an indication of whether to continue evaluating the body. Thus, if the body should be evaluated again, as would be the case if you were implementing an iteration tag, IterationTag.doAfterBody should return IterationTag.EVAL_BODY_AGAIN; otherwise, IterationTag.doAfterBody should return Tag.SKIP_BODY.

The following example reads the content of the body (which contains a SQL query) and passes it to an object that executes the query. Since the body does not need to be reevaluated, IterationTag.doAfterBody returns Tag.SKIP_BODY:

public class QueryTag extends BodyTagSupport {
	public int doAfterBody() throws JspTagException {
		BodyContent bc = getBodyContent();
		// get the bc as string
		String query = bc.getString();
		// clean up
		bc.clearBody();
		try {
			Statement stmt = connection.createStatement();
			result = stmt.executeQuery(query);
		} catch (SQLException e) {
			throw new JspTagException("QueryTag: " +
				e.getMessage());
		}
		return SKIP_BODY;
	}
} 
					

When you extend TagSupport, the doStartTag and doEndTag methods use the following return values (defined as constants):

  • doStartTag returns one of the following values:

    • Tag.EVAL_BODY_INCLUDE - Allow body text (including JSP code) between the start and end tags. Note, however, that body text is not available to the doEndTag method.

    • Tag.SKIP_BODY - Ignore body text. Any text between the start and end tags is not evaluated or displayed.

  • doEndTag returns one of the following values:

    • Tag.EVAL_PAGE - Continue evaluating the page.

    • Tag.SKIP_PAGE - Ignore the remainder of the page.

The following tag handler outputs messages from the doStartTag and doEndTag methods. These messages form the contents of the tag in the JSP page:

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

public class SimpleTag extends TagSupport {

	/**
	* Executes when the tag is started.
	*/
	public int doStartTag() throws JspException {
		try {
			pageContext.getOut().print("Hello from doStartTag()");
			// Allow text in the body of the tag.
			return EVAL_BODY_INCLUDE;
		} catch(IOException ioe) {
			throw new JspException(ioe.getMessage());
		}
	}

	/**
	* Executes with the end tag.
	*/
	public int doEndTag() throws JspException {
		try {
			pageContext.getOut().print("Hello from doEndTag()");
			// Continue evaluating the page.
			return EVAL_PAGE;
		} catch(IOException ioe) {
			throw new JspException(ioe.getMessage());
		}
	}
}
					

If your custom tag must modify body content, extend the BodyTagSupport class. It implements BodyTag. Provides the doInitBody and IterationTag.doAfterBody methods. Extend this class when your tag handler must modify body content. The doStartTag method can return BodyTag.EVAL_BODY_BUFFERED in addition to Tag.EVAL_BODY_INCLUDE and Tag.SKIP_BODY.

The BodyContent object is a subclass of JspWriter. JspWriter is the writer used internally for the JSP out variable. The BodyContent object is available to doInitBody, IterationTag.doAfterBody, and doEndTag through the bodyContent variable. You can integrate this object's contents with the original JspWriter in the doEndTag method. The BodyContent object contains methods that you use to write output as well as methods to read, clear, and retrieve its contents. For example, you can use bodyContent.getString() to retrieve the writer's contents, optionally modifying the contents before integrating them with the original JspWriter.

The servlet container invokes the doInitBody method if the doStartTag method returns BodyTag.EVAL_BODY_BUFFERED. Use this method to initialize the body content, if necessary. Then the tag handler processes the body, and invokes the IterationTag.doAfterBody method.

The IterationTag.doAfterBody method returns Tag.SKIP_BODY or IterationTag.EVAL_BODY_AGAIN. If it returns IterationTag.EVAL_BODY_AGAIN, the servlet container loops back and re-executes the body. This lets you loop over repetitive data, such as enumerations and database result sets.

The following example shows using the doInitBody and IterationTag.doAfterBody methods. It also shows how to integrate bodyContent output with the output stream:

public class TestBody extends BodyTagSupport {
	public int doStartTag() throws JspException {
		try {
			pageContext.getOut().print("doStartTag()");
			return EVAL_BODY_BUFFERED;
		} catch(IOException ioe) {
			throw new JspException(ioe.getMessage());
		}
	}

	public void doInitBody() throws JspException {
		try {  
			// Note, that this is a different writer than the one
			// you have in doStartTag and doEndTag.
			bodyContent.print("doInitBody()");
		} catch(IOException ioe) {
			throw new JspException(ioe.getMessage());
		} 
	}

	public int doAfterBody() throws JspException {
		try {
			// Note, that this is a different writer than the one
			// you have in doStartTag and doEndTag.
			bodyContent.print("doAfterBody()");
			// return IterationTag.EVAL_BODY_AGAIN;  
			// Use this to loop
			return SKIP_BODY;
		} catch(IOException ioe) {
			throw new JspException(ioe.getMessage());
		} 
	}

	public int doEndTag() throws JspException {
		try {  
			// Write from bodyContent writer to original writer.
			pageContext.getOut().print(bodyContent.getString());
			// Now we're back to the original writer.
			pageContext.getOut().print("doEndTag()");
			return EVAL_PAGE;
		} catch(IOException ioe) {
			throw new JspException(ioe.getMessage());
		} 
	}
}
					

Professional hosting     Belorussian informational portal         Free SCBCD 1.3 Study Guide     Free SCDJWS 1.4 Study Guide     SCDJWS 1.4 Quiz     Free IBM Certified Associate Developer Study Guide     Free SCJP 5.0 (Tiger) Study Guide     Free Mock Exam Engine     IBM Test 000-287. Enterprise Application Development with IBM WebSphere Studio, V5.0 Study Guide