In this chapter
Many times, you need a Web designer to design your Java Server Pages. The designer might be an expert with HTML, but might not know anything about Java. The <jsp:useBean>, <jsp:setProperty>,
and <jsp:getProperty>
tags allow you to make parts of your application accessible through tags, but you can't invoke bean methods without using either the <%
or <%=
tags. You can also provide some additional flexibility with the <jsp:include>
and <jsp:forward>
tags, but these tags are often overkill. You need a way to allow a Web designer to perform a specific operation without knowing any Java.
JSP Tag Extensions let you create new tags that a Web designer can insert directly into a Java Server Page. Through Tag Extensions, you can define tags that let you insert data into the output stream, include sections of a page only if certain conditions are met, and even modify the contents of the page itself before they are sent back to the browser.
To create a custom JSP tag, you must first create a Java class that acts as a tag handler. Whenever your custom tag appears in a Java Server Page, the JSP engine invokes your tag handler. If your custom tag doesn't care about the body text between its opening and closing tags, you can use the simple TagSupport
class, which implements the Tag
interface. If you need to access and possibly change the body text within the opening and closing tags, you must subclass the BodyTagSupport
class instead. The BodyTagSupport
class implements the BodyTag
interface, which allows you to access body text.
For example, suppose you define a custom tag named <mytags:DoSomething>
and you use it this way:
<mytags:DoSomething> Here is some text </mytags:DoSomething>
If your tag handler only implements the Tag interface, it can't see the body text (that is, "Here is some text"). All it can do is decide whether you can see the body text or not. Your custom tag can also generate its own output.
Listing 29.1 shows the HelloWorldTagHandler
class that inserts the familiar "Hello World!" message into the JSP response. Because it doesn't need to access its body text, it subclasses TagSupport.
Example 29.1. Source Code for HelloWorldTag.java
import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import java.io.*; public class HelloWorldTag extends TagSupport { public int doStartTag() throws JspException { try { JspWriter out = pageContext.getOut(); out.println("<h1>Hello World!</h1>"); } catch (IOException ioExc) { throw new JspException(ioExc.toString()); } return SKIP_BODY; } public int doEndTag() { return EVAL_PAGE; } }
Don't worry about the SKIP_BODY
and EVAL_PAGE
return values just yet. You'll see what they mean shortly.
Listing 29.2 shows a JSP that calls HelloWorldTag
via a tag named <mytag:hello>.
At this point, you don't know how to relate HelloWorldTag
to <mytag:hello>.
You will see that in the next section.
Example 29.2. Source Code for TestHello.jsp
<%@ taglib uri="/hello" prefix="mytag" %> <html> <body> <mytag:hello/> </body> </html>
Figure 29.1 shows the output from TestHello.jsp.
When you create a custom tag library you must also create a Tag Library Descriptor (TLD) that describes each tag in your tag library. Listing 29.3 shows the TLD for the HelloWorldTag
Java class.
Example 29.3. Source Code for hello.tld
<?xml version="1.0"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <shortname>hello</shortname> <uri></uri> <info> An example Hello World tag </info> <tag> <name>hello</name> <tagclass>HelloWorldTag</tagclass> </tag> </taglib>
The first few lines of hello.tld are pretty standard for an XML file. You must start with the <?xml?>
tag, of course. The next line defines the location of the Document Type Definition for this kind of document. The <!DOCTYPE
tag should be the same for all your TLDs.
<taglib>
is the root tag for a TLD and encloses all the other tags. Remember an XML document has a single root tag that encloses everything else in the document. The next few tags describe the tag library.
The <tlibversion>
tag describes the version number of the tag library, and the <jspversion>
tag indicates which version of JSP the tag library requires. The <shortname>
tag gives a short name for the tag library that might be used within a JSP Page Authoring tool. The idea is that you would load various tag libraries and see a list of the available libraries. The short name is the name you would see in the list. The <info>
tag gives the long description of the tag library. Finally, the <uri>
tag gives the normal URI for this tag library. Again, the URI is handy for a page authoring tool where you might have a local copy of the library, but when you build a JSP that uses the tag library, you might want to put the normal URI into the library. In other words, the page authoring tool might see the tag library on the hard drive with a path like c: taglibs hello.tld. You don't want the JSP to refer to the tag library with a URI of file:///c/taglibs/hello.tlb, because the JSP might be deployed on a machine that doesn't have a c: taglibs directory. You want a URI that works no matter where the JSP is deployed.
After the initial information describing the tag library, you can list the tags contained in the library. This tag library contains a single tag with a name of hello
(as indicated by the <name>
tag). The tag name, along with the prefix for the tag library, make up the full tag that you put in the JSP. In other words, you take the tag name hello
and combine it with the prefix specified in the JSP (mytag
in Listing 29.1) to get the full name of the tag, which is <mytag:hello>.
The reason for splitting the naming into two parts is that several people might make a tag named hello
in their tag libraries. You need a way to specify which tag you mean, so you must use a prefix to indicate which library you are referring to.
Finally, the <tagclass>
tag indicates the fully qualified pathname of the class that implements this tag.
Now that you have created the TLD file, you must deploy the tag library and your test Web page as a Web application. Create a directory called WEB-INF and in the WEB-INF directory, create a file called web.xml that looks like the file in Listing 29.4.
Example 29.4. Source Code for web.xml
<?xml version="1.0"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app> <display-name>Tag Demo</display-name> <description>An application for testing custom tags</description> <taglib> <taglib-uri>/hello</taglib-uri> <taglib-location>/WEB-INF/tld/hello.tld</taglib-location> </taglib> </web-app>
Most of the web.xml file should be familiar from the example in Chapter 28, "Packaging a JSP Application," where you packaged a Web application into a WAR file. There are a few tags that you haven't seen before, however. The <taglib>
tag defines a tag library that the Web application uses. The <taglib-uri>
tag defines the name that a JSP would use as the URI for this tag library. Look back at Listing 29.2 and you can see that TestHello.jsp specifies /hello as the URI for the tag library, which matches what you see in web.xml. The <taglib-location>
tag specifies the location of the hello.tld file, which is the file from Listing 29.3. According to the web.xml file, hello.tld should be stored in a directory called tld that is below the WEB-INF directory.
Now, under the WEB-INF directory, create a classes directory and copy the HelloWorldTag.class file to the classes directory. Make sure that TestHello.jsp is in the same directory as the WEB-INF directory. Now, create a file called tagdemo.war by going to the directory where WEB-INF and TestHello.jsp are located and entering the following command:
jar cvf tagdemo.war WEB-INF TestHello.jsp
The jar
command should respond with something like this:
added manifest adding: WEB-INF/(in = 0) (out= 0)(stored 0%) adding: WEB-INF/classes/(in = 0) (out= 0)(stored 0%) adding: WEB-INF/classes/HelloWorldTag.class(in = 839) (out= 486)(deflated 42%) adding: WEB-INF/tld/(in = 0) (out= 0)(stored 0%) adding: WEB-INF/tld/hello.tld(in = 457) (out= 268)(deflated 41%) adding: WEB-INF/web.xml(in = 441) (out= 262)(deflated 40%) adding: TestHello.jsp(in = 87) (out= 68)(deflated 21%)
Now follow the procedure from Chapter 28 to install the WAR file in your Web server. After the file is installed, you should be able to access TestHello.jsp and see the output shown previously in Figure 29.1.
If you are having trouble installing your custom tag library, see "Install Problems" in the "Troubleshooting" section at the end of this chapter.
Earlier in Listing 29.1, you saw that the doStartTag
method in the custom tag returns a value of SKIP_BODY
and the doEndTag
method returns a value of EVAL_PAGE.
These values tell the JSP engine how to handle the content between the start and end of the custom tag, and also whether to continue evaluating the rest of the page after the custom closing tag. When doStartTag
returns SKIP_BODY,
it tells the JSP engine to ignore the content between the start and end of the custom tag. If the doStartTag
returns EVAL_BODY_INCLUDE,
the data between the start and end tags is copied to the response and any nested tags are evaluated.
When doEndTag
returns EVAL_PAGE,
it tells the JSP engine to continue evaluating the rest of the page. If doEndTag
returns SKIP_PAGE,
the JSP engine ignores everything else in the JSP after the closing tag and returns the response to the browser.
Because you can control whether the JSP engine includes body text between the start and end of a tag, you can create tags that include text only if certain conditions are met.
Listing 29.5 shows a custom tag that only includes its content when the time of day is between 6 a.m. and 6 p.m.
Example 29.5. Source Code for DayTag.java
import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import java.util.*; public class DayTag extends TagSupport { public int doStartTag() throws JspException { // Get the time of day GregorianCalendar currTime = new GregorianCalendar(); // Get the hour of day int hour = currTime.get(Calendar.HOUR_OF_DAY); // If the time is between 6AM and 6PM, tell the JSP engine to // include the text between the start and end tag if ((hour >= 6) && (hour <= 18)) { return EVAL_BODY_INCLUDE; } else { // Otherwise, ignore the body text return SKIP_BODY; } } public int doEndTag() { return EVAL_PAGE; } }
You can easily make a NightTag
class that does the same test except that it only includes the body content when the hour is less than 6 or greater than 18. Listing 29.6 shows the daynight.tld file describing the DayTag
class and its companion NightTag
class.
Example 29.6. Source Code for daynight.tld
<?xml version="1.0"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <shortname>daynight</shortname> <uri></uri> <info> day tag to include text between 6am and 6pm, night to include text otherwise </info> <tag> <name>day</name> <tagclass>DayTag</tagclass> </tag> <tag> <name>night</name> <tagclass>NightTag</tagclass> </tag> </taglib>
The daynight.tld file should seem pretty familiar now. It doesn't contain any tags that you haven't already seen from the hello.tld file; it just defines two tags instead of one. Listing 29.7 shows a JSP that tests the day and night tags to make sure they work.
Example 29.7. Source Code for TestDayNight.jsp
<%@ taglib uri="/daynight" prefix="dn" %> <html> <body> <dn:day> <h1>My, what a beautiful day it is!</h1> </dn:day> <dn:night> <h1>I hate night, it's too dark for golf!</h1> </dn:night> </body> </html>
Figure 29.2 shows the output from TestDayNight.jsp when run during the day.
Figure 29.3 shows the output from TestDayNight.jsp when run at night. Notice that the text between the <dn:day>
and </dn:day>
doesn't show up.
Just like regular tags, custom tags can have attribute values. You need to provide get
and set
methods for each attribute. Although you enclose each attribute in quotes, you can have numeric attributes in your custom tag. The JSP engine performs the conversion automatically. Listing 29.8 shows a custom tag to display a checkerboard. The board has several options that can be changed by various attributes in the <xx:checkerboard>
tag.
Example 29.8. Source Code for CheckerboardTag.java
import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import java.io.*; public class CheckerboardTag extends TagSupport { // Variables to hold the attributes for the checkerboard protected int width = 40; protected int height = 40; protected int rows = 8; protected int cols = 8; protected String darkColor = "#000040"; protected String lightColor = "#FFFFC0"; public int doStartTag() throws JspException { try { JspWriter out = pageContext.getOut(); out.println("<table>"); // Count down so the bottom row is row 0 (it helps for // calculating the colors, the bottom left should be dark) for (int i=rows-1; i >= 0; i—) { // Start a new row with the specified height out.print("<tr height=""+height+"">"); // Loop through the columns for (int j=0; j < cols; j++) { // Start making the cell for this square out.print("<td width=""+width+"" bgcolor=""); // If row+column is even, make the square dark. The lower-left // corner should always be dark if ((i + j) % 2 == 0) { out.print(darkColor); } else { out.print(lightColor); } out.print(""> </td>"); } out.println("</tr>"); } out.println("</table>"); } catch (IOException ioExc) { throw new JspException(ioExc.toString()); } return SKIP_BODY; } public int doEndTag() { return EVAL_PAGE; } // Get/set methods, just like in a bean public int getHeight() { return height; } public void setHeight(int aHeight) { height = aHeight; } public int getWidth() { return width; } public void setWidth(int aWidth) { width = aWidth; } public int getRows() { return rows; } public void setRows(int aRows) { rows = aRows; } public int getCols() { return cols; } public void setCols(int aCols) { cols = aCols; } public String getDarkColor() { return darkColor; } public void setDarkColor(String aDarkColor) { darkColor = aDarkColor; } public String getLightColor() { return lightColor; } public void setLightColor(String aLightColor) { lightColor = aLightColor; } }
Now, just putting the attributes in the tag is not enough. You must also configure the attributes in the TLD file. Each attribute is defined using an <attribute>
tag. Within the <attribute>
tag there is a <name>
tag defining the name of the attribute, a <required>
tag indicating whether the attribute is required, and a tag called <rtexprvalue>.
You might have noticed other JSP examples where the value of a tag attribute was specified using a JSP expression (the <%=
tag). Evaluating custom tags where the attribute value can be generated at runtime is a difficult task for the JSP engine. Rather than allow all attribute expressions to be computed at runtime, the JSP engine wants you to explicitly mark the attributes whose values can be generated at runtime. Set the value of <rtexprvalue>
to yes
or true
if you need the attribute to be evaluated at runtime. The value is false
by default.
For the <required>
tag, you can use values of yes, no, true,
or false.
The <required>
tag is false
by default, meaning that if you don't explicitly say otherwise, an attribute is optional.
Listing 29.9 shows the TLD file for the CheckerboardTag
class.
Example 29.9. Source Code for checkerboard.tld
<?xml version="1.0"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <shortname>checkerboard</shortname> <uri></uri> <info> A tag that prints out a checkerboard pattern </info> <tag> <name>checkerboard</name> <tagclass>CheckerboardTag</tagclass> <attribute> <name>width</name> <required>no</required> </attribute> <attribute> <name>height</name> <required>no</required> </attribute> <attribute> <name>rows</name> <required>no</required> </attribute> <attribute> <name>cols</name> <required>no</required> </attribute> <attribute> <name>darkColor</name> <required>no</required> </attribute> <attribute> <name>lightColor</name> <required>no</required> </attribute> </tag> </taglib>
The checkerboard.tld file is similar to the other TLD files you have seen except that this one defines attributes for its tag. Listing 29.10 shows a JSP that tests out the CheckerboardTag
class.
Example 29.10. Source Code for TestCheckerboard.jsp
<%@ taglib uri="/checkerboard" prefix="cb" %> <html> <body> <cb:checkerboard width="50" height="50" rows="8" cols="8" darkColor="#000000" lightColor="#ffffff"/> </body> </html>
Figure 29.4 shows the output from TestCheckerboard.jsp.
If you are having trouble setting attribute values in a custom tag, see "Attribute Values" in the "Troubleshooting" section at the end of this chapter.
One of the most interesting features of the JSP tag extension mechanism is that the tags can access their own body content. That is, a tag can see the text contained between its begin and end tags and even modify that text.
Processing body text is a little more involved and requires a specialized tag interface. A basic tag implements an interface called Tag
and usually inherits from the TagSupport
class. A tag that processes its body text must implement the BodyTag
interface and usually inherits from BodyTagSupport.
Because the BodyTag
interface extends the Tag
interface, it includes the doStartTag
and doEndTag
methods. A tag implementing the BodyTag
interface might still return SKIP_BODY
from the doStartTag
method to indicate that the JSP engine should not evaluate the text between the beginning and end of the tag. Instead of returning EVAL_BODY_INCLUDE,
however, a body tag must return EVAL_BODY_TAG
to include its body text.
A custom tag that implements the
BodyTag
must not return EVAL_BODY_INCLUDE,
otherwise the JSP engine reports an error. If a custom tag that only implements the Tag interface returns EVAL_BODY_TAG,
the JSP engine also reports an error. In other words, EVAL_BODY_INCLUDE
can only be used with non-body tags and EVAL_BODY_TAG
can only be used with a body tag.
Body tags have a very peculiar way of operating on body text. When the JSP engine first starts evaluating the body text, it calls the doInitBody
method in the custom tag. There is no return value for doInitBody
and it is intended for you to perform initialization in this method. After the JSP engine evaluates the body content, it calls doAfterBody
in the custom tag. Whenever doAfterBody
is called, the custom tag can access the current body content by calling getBodyContent.
The peculiar thing is that if doAfterBody
returns EVAL_BODY_TAG,
the JSP engine re-evaluates the current body content and calls doAfterBody
again! The JSP engine finally accepts the content after doAfterBody
returns SKIP_BODY
.
That sounds very counter-intuitive, and it is. Before delving into some of the complexities, take a look at a very minimal body tag. Listing 29.11 shows a body tag that prints its body text to the response (in other words, it shows the text between its begin and end tags as if the tags weren't there).
Example 29.11. Source Code for TestBodyTag.java
import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import java.io.*; public class TestBodyTag extends BodyTagSupport { public int doStartTag() throws JspException { return EVAL_BODY_TAG; } public int doEndTag() { return EVAL_PAGE; } public void doInitTag() { } public int doAfterBody() throws JspException { // Get the current body content BodyContent body = getBodyContent(); try { // Ask the body content to write itself out to the response body.writeOut(body.getEnclosingWriter()); } catch (IOException exc) { throw new JspException(exc.toString()); } // Tell the JSP engine that the body content has been evaluated return SKIP_BODY; } } java
If you are having trouble getting a body tag to work, see "Body Tags" in the "Troubleshooting" section at the end of this chapter.
In Listing 29.11, you see that you can write the body content into the response by calling body.writeOut(body.getEnclosingWriter()).
The body.writeOut
method writes the contents of the body to any Writer
object. The getEnclosingWriter
method returns the writer for the section of the response that this body tag is contained in. You can use body.getReader
to get a Reader
object that lets you read the contents of the body, or just call body.getString
to get the contents as a string. In fact, an alternate way to write out the contents of a body is
body.getEnclosingWriter().println(body.getString());
Make sure you always have a case where your doAfterBody
method returns SKIP_BODY.
If you only return EVAL_TAG_BODY,
the JSP engine will get stuck in an infinite loop calling your doAfterBody
method over and over.
The odd looping behavior with doAfterBody
gets even more confusing with the fact that the JSP engine does not re-parse the body content after each call to doAfterBody.
In other words, if you write out some additional text or tags, those do not get added to the current body content. Why have this looping structure at all, then? Why can't you just use a for-loop? The reason for the looping behavior is that you might have a body tag with some nested custom tags that change the values of some variables, like this java:
<xx:bodyTag> Some HTML text <xx:computeNewValue/> Some more text </xx:bodyTag>
In this example, the <xx:computeNewValue>
tag calculates some value that helps determine when the <xx:bodyTag>
tag should stop looping. It doesn't really matter what the values are; the important point is that the tag handler for <xx:computeNewValue>
is called every time the body text is re-evaluated. If you tried to do a for-loop to print out the body text, you wouldn't have a way to call the tag handler for <xx:computeNewValue>
java.
Your custom tags can define scripting variables that are accessible to your Java Server Pages. In fact, you can even get the JSP engine to add a Java variable to the generated servlet to hold the value of your script variable. All you need to do is create a special TagExtraInfo
class that describes the scripting variables your tag might define.
Listing 29.12 shows a subclass of TagExtraInfo
that defines a scripting variable called scriptVar.
The tag itself doesn't need to know about the extra info class, but it does need to put the scripting variables into the page context so the Java Server Page can extract the value of each variable and place it in a local Java variable. Listing 29.13 shows a custom tag that puts a value into the page context.
Example 29.13. Source Code for ScriptTag.java
import javax.servlet.jsp.tagext.*; import javax.servlet.jsp.*; import java.io.*; public class ScriptTag extends TagSupport { public int doStartTag() throws JspException { pageContext.setAttribute("scriptVar", "This is the script variable"); return SKIP_BODY; } public int doEndTag() { return EVAL_PAGE; } }
Listing 29.14 shows a Java Server Page that calls the ScriptTag
custom tag and then accesses scriptVar
as if it were a local variable.
You might be wondering how a value gets from the page context into a Java variable automatically. It isn't as automatic as you might think. According to the JSP specification, the JSP engine is responsible for getting the value from the page context and copying it to the local variable. If you examine the servlet generated from a JSP using a tag-generated scripting variable, you'll see a line that copies the value out of the page context.
To match a TagExtraInfo
class to its associated tag, you must include a <teiclass>
tag in the Tag Library Descriptor for the custom tag. Listing 29.15 shows the TLD for the ScriptTag
class.
Example 29.15. Source Code for scripttag.tld
<?xml version="1.0"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <shortname>scripttag</shortname> <uri></uri> <info> An example scripting variable tag </info> <tag> <name>setVar</name> <tagclass>ScriptTag</tagclass> <teiclass>ScriptExtraInfo</teiclass> </tag> </taglib>
If you are having trouble defining scripting variables, see "Scripting Variables" in the "Troubleshooting" section at the end of this chapter.
The TagExtraInfo
class actually serves two purposes. In addition to defining scripting variables, it has an isValid
method that allows you to validate attribute values at JSP compile time. The isValid
method takes an argument of type TagData
and returns true
if the combination of attributes in TagData
is valid, or false
if there is an error in the attributes. The idea here is that you can't define what constitutes a good attribute value or a good set of values without evaluating the values. By evaluating them when the JSP is compiled, you avoid any unnecessary runtime overhead.
The TagData
class contains the following methods for examining the attributes for a tag:
Object getAttribute(String attrName) Enumeration getAttributes() String getAttributeString(String attrName) String getId() void setAttribute(String attrName, Object value)
Most of these methods are self-explanatory. The getId
method is a shortcut for getAttributeString. ("id")
Because some attribute values must be evaluated at request time instead of compile time, the getAttribute
method returns a special object named TagData.REQUEST_TIME_VALUE
for any value that must be evaluated at request time. A request time attribute value is a value that can be described using the <%= %>
tag pair.
3.141.166.190