JSP Tags
CONTENTS
Custom Tags
JSP 1.0 Tags
The descriptor file
Simple Tags
Tags with Attributes
Tags with Bodies
Including the Body
Repeating the Body
Reading the Body
Scripting Variables in the Body
Scripting Variables in the Rest of the Page
Dynamic Attributes
Cooperating Tags
JSP 2.0 Tag Files
Directives
The tag directive
The include directive
The taglib directive
The attribute directive
The variable directive
Dynamic attributes
JSP 2.0 Tags
Introduction
Example

Custom Tags

Two mechanisms now supported:
Typical tag scenarios:
to top

JSP 1.0 Tags

These tags are implemented via Java classes, known as tag handlers, and require a descriptor file. This is the classic approach, starting from JSP 1.0.
Tag handlers implement the Tag or BodyTag interfaces, and can be derived from TagSupport and BodyTagSupport. All of these are in the javax.servlet.jsp.tagext package.
Typical methods to be implemented:

The descriptor file

The descriptor is a .tld file, in XML format. It starts with the xml declaration and doctype:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> ... </taglib>
The following elements are permitted as children of <taglib>:
tlib-version The version of the library
jsp-version The JSP version targeted
short-name optinal name
uri A URI that uniquely identifies the library
display-name optional name
small-icon optional icon
large-icon optional icon
description optional description
listener Defines a listener; includes a nested listener-class element
tag Defines a tag (see below)
The <tag> element has the following child elements:
name The tag name
tag-class Fully qualified name of the implementation class
tei-class Fully qualified name of the TagExtraInfo class, used to define scripting variables that are exported to the calling JSP. This is not needed if there are no exported scripting variables. Furthermore, the <variable> element can be used instead.
body-content The body content type: 'empty', 'JSP' or 'tagdependent'
display-name Optional display name
small-icon Optional small icon
large-icon Optional large icon
description Optional description
variable Defines an exported scripting variable (see below)
attribute Defines an attribute (see below)
The variable element defines an exported scripting variable and has the following attributes:
name-given The name of the variable
name-from-attribute The name of the attribute whose value will define the name of the variable. Exactly one of name-given and name-from-attribute must be supplied.
variable-class The class of the variable. The default is java.lang.String.
declare Whether the variable refers to a new object. The default is true.
scope The scope of the variable: NESTED, AT_BEGIN, AT_END.
Finally, the <attribute> element has the following child elements:
name The name of the attribute
required Whether the attribute is required or not: 'true', 'false', 'yes' or 'no'
rtexprvalue Whether a runtime expression is accepted: 'true', 'false', 'yes' or 'no'
type The fully-qualified type of the variable

Simple Tags

Simple tags have no attributes, do not evaluate their bodies, and export no scripting variables. They are used like this:
<asitl:now/>
The JSP file, test-simple.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html> <head> <title>JSP Tag Test</title> </head> <body> It is now <asitl:now/> </body> </html>
The output:
It is now Mon May 02 10:12:02 GMT-05:00 2005
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> <tag> <name>now</name> <tag-class>net.alethis.tags.CurrentDateTag</tag-class> <body-content>empty</body-content> </tag> </taglib>
The Java class, CurrentDateTag.java:
package net.alethis.tags; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class CurrentDateTag extends TagSupport { public int doStartTag() throws JspException { try { Date d = new Date(); pageContext.getOut().print(d.toString()); } catch (Exception e) { throw new JspTagException("error in CurrentDateTag: " + e.getMessage()); } return SKIP_BODY; } public int doEndTag() { return EVAL_PAGE; } }
The jar file structure:
test-simple.jsp WEB-INF asitl.tld classes net alethis tags CurrentDateTag.class

Tags with Attributes

The attributes are received in the tag class via setter methods.
The JSP file, test-attributes.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core' %> <html3 <head> <title>JSP Tag Test</title> </head> <body> <c:set var="x" value="38"/> The square of ${x} is <asitl:power exponent="2" value="${x}"/> <br> The cube of ${x} is <asitl:power exponent="3" value="${x}"/> </body> </html>
We're using the JSTL to set up the variable x.
And the output:
The square of 38 is 1444 The cube of 38 is 54872
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>power</name> <tag-class>net.alethis.tags.PowerTag</tag-class> <body-content>empty</body-content> <attribute> <name>exponent</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>value</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
The implementation class, PowerTag.java:
package net.alethis.tags; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class PowerTag extends TagSupport { private String exponentString; private String valueString; public void setExponent(String s) { exponentString = s; } public void setValue(String s) { valueString = s; } public int doStartTag() throws JspException { try { int exp = Integer.parseInt(exponentString); int val = Integer.parseInt(valueString); int result = 1; for (int i = 0; i < exp; i++) { result = result * val; } pageContext.getOut().print(result); } catch (Exception e) { throw new JspTagException("error in PowerTag: " + e.getMessage()); } return SKIP_BODY; } public int doEndTag() { return EVAL_PAGE; } }
Note that the tag only works for positive exponents!
The jar file structure (including the necessary JSTL files):
test-attributes.jsp WEB-INF asitl.tld classes net alethis tags CurrentDateTag.class lib jstl.jar standard.jar

Tags with Bodies

The techniques to be used depend on what is to be done with the body.
The first distinction is whether the tag 'interacts' with the body. Interaction is required if the tag needs to read or modify the body content.
A second distinction is whether the tag implements some kind of iteration, where the body content is evaluated multiple times.
The body-content element in the descriptor file should be set to 'tagdependent' if the body is plain template text, or 'JSP' if it includes expressions, jsp tags or custom tags.
The general possibilities are:
No interaction, no iteration Implement Tag, or inherit from TagSupport; the doStartTag() method should return EVAL_BODY_INCLUDE
No interaction, with iteration Implement IterationTag, or inherit from TagSupport; doStartTag() should return EVAL_BODY_INCLUDE (or SKIP_BODY) and doAfterBody() should return EVAL_BODY_AGAIN as long as further iterations are required, and SKIP_BODY at the end.
Interaction, no iteration Implement BodyTag, or inherit from BodyTagSupport; implement doInitBody() and doAfterBody(); the doStartTag() method should return EVAL_BODY_BUFFERED
Interaction and iteration Implement BodyTag, or inherit from BodyTagSupport; implement doInitBody() and doAfterBody(); thedoStartTag() and doAfterBody() should return EVAL_BODY_AGAIN as long as further iterations are required, and SKIP_BODY at the end.
To summarize, doStartTag() should return:
  • SKIP_BODY - if the body does not need to be evaluated
  • EVAL_BODY_INCLUDE - if the body is to be included, but not read or modified by the tag (the container will not make the BodyContent object available)
  • EVAL_BODY_BUFFERED - if the body is to be read or modified; in this case the container will call setBodyContent() on the tag to initialize the bodyContent; this method is available only in the BodyTag interface, so it is an error to return this value from a tag that only implements Tag

Including the Body

This example conditionally includes the body depending on the value of an attribute.
The JSP file, test-include-body.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core' %> <html3 <head> <title>JSP Tag Test</title> </head> <body> <c:set var="x" value="38"/> <asitl:ifpositive value="${x}"> First number is positive </asitl:ifpositive> <c:set var="x" value="-38"/> <asitl:ifpositive value="${x}"> Second number is positive </asitl:ifpositive> </body> </html>
The output:
First number is positive
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>ifpositive</name> <tag-class>net.alethis.tags.IfPositiveTag</tag-class> <body-content>tagdependent</body-content> <attribute> <name>value</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
The implementation class, PowerTag.java:
package net.alethis.tags; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class IfPositiveTag extends TagSupport { private String valueString; public void setValue(String s) { valueString = s; } public int doStartTag() throws JspException { try { int val = Integer.parseInt(valueString); if (val < 0) { return SKIP_BODY; } else { return EVAL_BODY_INCLUDE; } } catch (Exception e) { throw new JspTagException("error in IfPositiveTag: " + e.getMessage()); } } public int doEndTag() { return EVAL_PAGE; } }
The jar file structure (including the necessary JSTL files):
test-include-body.jsp WEB-INF asitl.tld classes net alethis tags IfPositiveTag.class lib jstl.jar standard.jar

Repeating the Body

This example repeats a body a specified number of times.
The JSP file, test-iterate.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html3 <head> <title>JSP Tag Test</title> </head> <body> And the answer is<asitl:repeat count="17">.</asitl:repeat> 42! </body> </html>
The output:
And the answer is................. 42!
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>repeat</name> <tag-class>net.alethis.tags.RepeatTag</tag-class> <body-content>tagdependent</body-content> <attribute> <name>count</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
The implementation class, RepeatTag.java:
package net.alethis.tags; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class RepeatTag extends TagSupport { private String countString; private int count; public void setCount(String s) { countString = s; } public int doStartTag() throws JspException { try { count = Integer.parseInt(countString); if (count > 0) { return EVAL_BODY_AGAIN; } else { return SKIP_BODY; } } catch (Exception e) { throw new JspTagException("error in RepeatTag: " + e.getMessage()); } } public int doAfterBody() { count--; if (count > 0) { return EVAL_BODY_AGAIN; } else { return SKIP_BODY; } } public int doEndTag() { return EVAL_PAGE; } }
The jar file structure (including the necessary JSTL files):
test-iterate.jsp WEB-INF asitl.tld classes net alethis tags RepeatTag.class

Reading the Body

This example shows a tag that reads its body, and replaces the body text with generated text. Tags of this nature must implement BodyTag, or inherit from BodyTagSupport.
The content of the body is accessible in doAfterBody via the getBodyContent method, which returns a BodyContent object. The getString() method on this object gets the content. To write to the output stream, use the JspWriter obtained by calling getEnclosingWriter on the BodyContent object. This ensures that the content will be available to enclosing tags.
The JSP file, test-read-body.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html> <head> <title>JSP Tag Test</title> </head> <body> 3 * 7 = <asitl:evaluate>3 * 7</asitl:evaluate> <br> 14 + 8 = <asitl:evaluate>14 + 8</asitl:evaluate> <br> 723 - 194 = <asitl:evaluate>723 - 194</asitl:evaluate> <br> 37 / 4 = <asitl:evaluate>37 / 4</asitl:evaluate> </body> </html>
The output:
3 * 7 = 21 14 + 8 = 22 723 - 194 = 529 37 / 4 = 9
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>evaluate</name> <tag-class>net.alethis.tags.EvaluateTag</tag-class> <body-content>tagdependent</body-content> </tag> </taglib>
The implementation class, EvaluateTag.java. The doStartTag() method must return EVAL_BODY_BUFFERED in order that the BodyContent object be made available.
package net.alethis.tags; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class EvaluateTag extends BodyTagSupport { public int doStartTag() throws JspException { return EVAL_BODY_BUFFERED; } public int doAfterBody() throws JspException { try { BodyContent bc = getBodyContent(); String expression = bc.getString(); bc.clearBody(); StringTokenizer st = new StringTokenizer(expression); int first = Integer.parseInt(st.nextToken()); String op = st.nextToken(); int second = Integer.parseInt(st.nextToken()); int result = 0; if (op.equals("+")) { result = first + second; } else if (op.equals("-")) { result = first - second; } else if (op.equals("*")) { result = first * second; } else if (op.equals("/")) { result = first / second; } JspWriter out = bc.getEnclosingWriter(); out.print(result); bodyContent.clearBody(); return SKIP_BODY; } catch (Exception e) { throw new JspTagException("error in EvaluateTag: " + e.getMessage()); } } public int doEndTag() { return EVAL_PAGE; } }
The jar file structure:
test-read-body.jsp WEB-INF asitl.tld classes net alethis tags EvaluateTag.class

Scripting Variables in the Body

This example shows a tag that evaluates its body iteratively, and provides scripting variables used in the body. Since we're not actually reading or modifying the body, we can inherit from TagSupport. The doStartTag() method must return EVAL_BODY_INCLUDE rather than EVAL_BODY_BUFFERED to indicate the BodyContent object is not needed; otherwise, the container attempts to call the method that sets the BodyContent, which is only defined in the BodyTag interface.
Also, since the scripting variables are used only in the body of the custom tag, the NESTED scope is appropriate.
The JSP file, test-variable.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html> <head> <title>JSP Tag Test</title> </head> <body> <asitl:powertable from="1" to="10"> <tr> <td>${value}</td> <td>${squared}</td> <td>${cubed}</td> </tr> </asitl:powertable> </body> </html>
The output:
Value Square Cube
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>powertable</name> <tag-class>net.alethis.tags.PowerTableTag</tag-class> <body-content>JSP</body-content> <attribute> <name>from</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>to</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <variable> <name-given>value</name-given> <variable-class>java.lang.String</variable-class> <declare>true</declare> <scope>NESTED</scope> </variable> <variable> <name-given>squared</name-given> <variable-class>java.lang.String</variable-class> <declare>true</declare> <scope>NESTED</scope> </variable> <variable> <name-given>cubed</name-given> <variable-class>java.lang.String</variable-class> <declare>true</declare> <scope>NESTED</scope> </variable> </tag> </taglib>
The implementation class, PowerTableTag.java:
package net.alethis.tags; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class PowerTableTag extends TagSupport { private String fromString; private String toString; private int from; private int to; private int curr; public void setFrom(String s) { fromString = s; } public void setTo(String s) { toString = s; } public int doStartTag() throws JspException { try { pageContext.getOut().println("<table border='1'>"); pageContext.getOut().println("<tr>"); pageContext.getOut().println("<th>Value</th>"); pageContext.getOut().println("<th>Square</th>"); pageContext.getOut().println("<th>Cube</th>"); pageContext.getOut().println("</tr>"); from = Integer.parseInt(fromString); to = Integer.parseInt(toString); curr = from; pageContext.setAttribute("value", String.valueOf(curr)); pageContext.setAttribute("squared", String.valueOf(curr * curr)); pageContext.setAttribute("cubed", String.valueOf(curr * curr * curr)); return EVAL_BODY_INCLUDE; } catch (Exception e) { throw new JspTagException("error in RepeatTag: " + e.getMessage()); } } public int doAfterBody() { curr++; if (curr <= to) { pageContext.setAttribute("value", String.valueOf(curr)); pageContext.setAttribute("squared", String.valueOf(curr * curr)); pageContext.setAttribute("cubed", String.valueOf(curr * curr * curr)); return EVAL_BODY_AGAIN; } else { return SKIP_BODY; } } public int doEndTag() throws JspException { try { pageContext.getOut().println("</table>"); return EVAL_PAGE; } catch (Exception e) { throw new JspTagException("error in RepeatTag: " + e.getMessage()); } } }
The jar file structure:
test-variable.jsp WEB-INF asitl.tld classes net alethis tags PowerTableTag.class

Scripting Variables in the Rest of the Page

This example shows a tag that provides a scripting variable for the rest of the page. The appropriate scope is AT_END.
The JSP file, test-variable2.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html> <head> <title>JSP Tag Test</title> </head> <body> <asitl:provideBrowser/> Your browser is ${browser}. </body> </html>
The output:
Your browser is Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8b) Gecko/20050201 Firefox/1.0+.
The descriptor file, asitl.tld:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> <tag> <name>provideBrowser</name> <tag-class>net.alethis.tags.BrowserTag</tag-class> <body-content>empty</body-content> <variable> <name-given>browser</name-given> <variable-class>java.lang.String</variable-class> <declare>true</declare> <scope>AT_END</scope> </variable> </tag> </taglib>
The implementation class, BrowserTag.java:
package net.alethis.tags; import javax.servlet.http.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class BrowserTag extends TagSupport { public int doStartTag() throws JspException { String browser = ((HttpServletRequest) pageContext.getRequest()).getHeader("User-Agent"); pageContext.setAttribute("browser", browser); return SKIP_BODY; } public int doEndTag() throws JspException { return EVAL_PAGE; } }
The jar file structure:
test-variable2.jsp WEB-INF asitl.tld classes net alethis tags BrowserTag.class

Dynamic Attributes

The ability of a tag to handle dynamic attributes was introduced in JSP 2.0. The tag handler must implement DynamicAttributes, which provides the method setDynamicAttribute(), and the tld descriptor file must indicate that the tag accepts dynamic attributes (and must use the JSP 2.0 DTD!).
The JSP file, test-dynamic.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html> <head> <title>JSP Tag Test</title> </head> <body> <asitl:mapTable a="1" b="5" c="14" d="42"/> <br> <asitl:mapTable vvv="niece" www="daughter" xxx="sister" yyy="mother" zzz="aunt"/> <br> <asitl:mapTable rr="green" gg="blue" bb="red"/> </body> </html>
The output:
a 1
b 5
c 14
d 42

vvv niece
www daughter
xxx sister
yyy mother
zzz aunt

rr green
gg blue
bb red
The descriptor file, asitl.tld. Note that it uses the JSP 2.0 format. In particular, for validation, an XML Schema is used instead of a DTD.
<?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/web-jsptaglibrary_2_0.xsd' version='2.0'> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>mapTable</name> <tag-class>net.alethis.tags.MapTableTag</tag-class> <body-content>empty</body-content> <dynamic-attributes>true</dynamic-attributes> </tag> </taglib>
The implementation class, MapTableTag.java:
package net.alethis.tags; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class MapTableTag extends TagSupport implements DynamicAttributes { private List names = new ArrayList(); private List values = new ArrayList(); public void setDynamicAttribute(String ns, String name, Object value) { names.add(name); values.add(value); } public int doStartTag() throws JspException { try { JspWriter out = pageContext.getOut(); out.println("<table border='1'>"); for (int i = 0; i < names.size(); i++) { out.print("<tr>"); out.print("<td>"); out.print((String) names.get(i)); out.println("</td>"); out.print("<td>"); out.print(values.get(i).toString()); out.println("</td>"); out.print("</tr>"); } out.println("</table>"); return SKIP_BODY; } catch (Exception e) { throw new JspTagException("error in MapTableTag: " + e.getMessage()); } } public int doEndTag() throws JspException { return EVAL_PAGE; } }
The jar file structure:
test-dynamic.jsp WEB-INF asitl.tld classes net alethis tags MapTableTag.class

Cooperating Tags

Custom tags in a hierarchy can cooperate with each other. The basic mechanism is for a child tag implementation to access the implementation of a parent tag, or indeed, of any ancestor tag. TagSupport includes getParent() and setParent() methods that are used to traverse the hierarchy, and it also includes findAncestorWithClass(), to search for an arbitary ancestor by class. This is the method used in the following example.
The JSP file, test-wall.jsp:
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html> <head> <title>JSP Test Tag</title> </head> <body> <asitl:wall start='10'> <asitl:bottle/> <br> </asitl:wall> </body> </html>
The output:
10 bottles of beer on the wall 9 bottles of beer on the wall 8 bottles of beer on the wall 7 bottles of beer on the wall 6 bottles of beer on the wall 5 bottles of beer on the wall 4 bottles of beer on the wall 3 bottles of beer on the wall 2 bottles of beer on the wall 1 bottles of beer on the wall 0 bottles of beer on the wall
The descriptor file, asitl.tld.
<?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/web-jsptaglibrary_2_0.xsd' version='2.0'> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>wall</name> <tag-class>net.alethis.tags.WallTag</tag-class> <body-content>JSP</body-content> <attribute> <name>start</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> <tag> <name>bottle</name> <tag-class>net.alethis.tags.BottleTag</tag-class> <body-content>empty</body-content> </tag> </taglib>
The first implementation class, WallTag.java. It iterates the body from the start count down to zero:
package net.alethis.tags; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class WallTag extends TagSupport { private String startString; private int curr; public void setStart(String s) { startString = s; } public int getCurrentCount() { return curr; } public int doStartTag() throws JspException { try { curr = Integer.parseInt(startString); if (curr > 0) { return EVAL_BODY_INCLUDE; } else { return SKIP_BODY; } } catch (Exception e) { throw new JspTagException("error in WallTag: " + e.getMessage()); } } public int doAfterBody() { curr--; if (curr >= 0) { return EVAL_BODY_AGAIN; } else { return SKIP_BODY; } } public int doEndTag() { return EVAL_PAGE; } }
The second implementation class, BottleTag.java. It accesses the wall tag in order to determine the current count of the number of bottles left on the wall.
package net.alethis.tags; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class BottleTag extends TagSupport { public int doStartTag() throws JspException { try { WallTag wall = (WallTag) findAncestorWithClass(this, WallTag.class); int curr = wall.getCurrentCount(); pageContext.getOut().println(curr + " bottles of beer on the wall"); return SKIP_BODY; } catch (Exception e) { throw new JspTagException("error in BottleTag: " + e.getMessage()); } } public int doEndTag() { return EVAL_PAGE; } }
The jar file structure:
test-wall.jsp WEB-INF asitl.tld classes net alethis tags BottleTag.class WallTag.class
to top

JSP 2.0 Tag Files

With JSP 2.0, two new mechanisms were introduced to simplify the creation of custom tags. The first of these is known as 'tag files', which implement custom tags by means of files which look very much like JSP pages. They have the .tag or .tagx extension (classic style vs. XML style), and included fragments have a .tagf extension. This mechanism is particularly useful for tags which include a lot of template text.
The files should be placed under WEB-INF/tags.
Implicit objects available:
jspContext replaces pageContext to reduce dependency on Java.
Example: simple tag with no body or attributes:
<%@ tag import="java.util.*" import="java.text.*" %> <% DateFormat df = DateFormat.getDateInstance(DateFormat.LONG); Date d = new Date(System.currentTimeMillis()); out.println(df.format(d)); %>
Store this as currDate.tag in WEB-INF/tags, and the <currDate> tag becomes available. Here's a typical JSP file:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %> <html> <head> </head> <body> Today is <util:currDate/>. </body> </html>

Directives

Directives available in tag files:
tag similar to the page directive for JSP pages
include includes other tag files
taglib use another custom tag library
attribute declare an attribute
variable define a scripting variable that becomes visible in the JSP page

The tag directive

The tag directive has the following attributes:
body-content describes the treatment of the body: 'empty', 'tagdependent' or 'scriptless'
import imports classes or packages; this attribute can appear multiple times
pageEncoding as in the page directive
isELIgnored as in the page directive
dynamic-attributes specifies the name of a map that will receive all undeclared attributes
language the scripting language, must be 'java' currently
display-name for tools - optional - default is the tag file name, minus extension
small-icon for tools
large-icon for tools
description a description of the tag
example informal description of how the tag is used
There may be multiple tag directives, but only the import attribute can appear multiple times among the union of all of their attributes.

The include directive

The include directive includes other files. It has a single 'file' attribute.
<%@ include file='commonheader.tagf' %>

The taglib directive

The taglib directive is as for JSP pages.

The attribute directive

The attribute directive declares an attribute that the tag accepts. The attributes of the attribute directive are:
name the name of the attribute
required true or false
rtexprvalue true or false - indicates whether runtime expressions are accepted; true is the default
type the value type - default is java.lang.String
fragment true or false - should the value be evaluated by the container (false), or passed directly to the tag handler (true)
description a description for the attribute
This tag creates a number of copies of a string; save it as WEB-INF/tags/repeater.tag:
<%@ attribute name="count" type="java.lang.Integer" required="true" %> <%@ attribute name="value" type="java.lang.String" required="true" %> <%! private String repeater(Integer count, String s) { int n = count.intValue(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < n; i++) { sb.append(s); } return sb.toString(); } %> <% out.println(repeater(count, value)); %>
This tag will accept run-time expressions for both attributes, since true is the default for rtexprvalue. Use the tag like this:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %> <html> <head> </head> <body> Let's get some sleep! <util:repeater count='${3 * 10}' value='zzz'/> </body> </html>

The variable directive

The variable directive exports a variable to the JSP page. The attributes are:
name-given The variable name that will be available in the calling JSP page.
name-from-attribute Specifies the name of an attribute, whose value is the name of the variable that will be available in the calling JSP page. Exactly one of name-given or name-from-attribute must be supplied.
alias A locally scoped variable which will store the variable's value. Used only with name-from-attribute.
variable-class The class of the variable. Default is java.lang.String.
declare Indicates whether the variable is declared in the calling JSP page or tag file. Default is true. Not entirely clear what this means!
scope The variable's scope; possible values are AT_BEGIN, AT_END and NESTED. NESTED is the default.
description A description for the variable.
The variable value is set by using setAttribute() on the jspContext object.
Here's an example, which uses the default NESTED setting for the scope. That means that the variable values will be available only within the body of the tag. The <doBody> tag at the end indicates that the tag body should be rendered, and the variable values will be available then.
<%@ tag import="java.util.*" %> <%@ attribute name="cityName" required="true" %> <%@ variable name-given="province" %> <%@ variable name-given="population" variable-class="java.lang.Integer" %> <% if ("Toronto".equals(cityName)) { jspContext.setAttribute("province", "Ontario"); jspContext.setAttribute("population", new Integer(2553400)); } else if ("Montreal".equals(cityName)) { jspContext.setAttribute("province", "Quebec"); jspContext.setAttribute("population", new Integer(2195800)); } else { jspContext.setAttribute("province", "Unknown"); jspContext.setAttribute("population", new Integer(-1)); } %> <jsp:doBody/>
And the JSP page:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %> <html> <head> </head> <body> <% pageContext.setAttribute("cityName", "Montreal"); %> <util:lookup cityName="${cityName}"> ${cityName}'s province is ${province}. ${cityName}'s population is approximately ${population / 1000000} million. </util:lookup> </body> </html>
This is a bit less wordy using JSTL:
<%@ tag import="java.util.*" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ attribute name="cityName" required="true" %> <%@ variable name-given="province" %> <%@ variable name-given="population" %> <c:choose> <c:when test="cityName eq 'Toronto'> <c:set var="province" value="Ontario"/> <c:set var="population" value="2553400"/> </c:when> <c:when test="cityName eq 'Montreal'> <c:set var="province" value="Quebec"/> <c:set var="population" value="2195800"/> </c:when> <c:otherwise> <c:set var="province" value="Unknown"/> <c:set var="population" value="-1"/> </c:otherwise> </c:choose> %> <jsp:doBody/>
and the JSP:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> </head> <body> <c:set var="cityName" value="Montreal"/> <util:lookup cityName="${cityName}"> ${cityName}'s province is ${province}. ${cityName}'s population is approximately ${population / 1000000} million. </util:lookup> </body> </html>
Or let's do it XML! This would be lookup2.tagx:
<?xml version='1.0'?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"> <jsp:directive.tag import="java.util.*"/> <jsp:directive.taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"/> <jsp:directive.attribute name="cityName" required="true"/> <jsp:directive.variable name-given="province"/> <jsp:directive.variable name-given="population"/> <c:choose> <c:when test="cityName eq 'Toronto'> <c:set var="province" value="Ontario"/> <c:set var="population" value="2553400"/> </c:when> <c:when test="cityName eq 'Montreal'> <c:set var="province" value="Quebec"/> <c:set var="population" value="2195800"/> </c:when> <c:otherwise> <c:set var="province" value="Unknown"/> <c:set var="population" value="-1"/> </c:otherwise> </c:choose> </jsp:root>
and the JSP:
<?xml version='1.0'?> <jsp:root version='2.0' xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:util="urn:jsptagdir:/WEB-INF/tags" xmlns:c="http://java.sun.com/jsp/jstl/core"> <jsp:directive.page contentType="text/html"/> <html> <head> </head> <body> <c:set var="cityName" value="Montreal"/> <util:lookup cityName="${cityName}"> ${cityName}'s province is ${province}. ${cityName}'s population is approximately ${population / 1000000} million. </util:lookup> </body> </html> </jsp:root>
The scope is a bit hard to understand. Here are the interpretations:
EVENT NESTED AT_BEGIN AT_END
beginning of tag file save nothing nothing
before any fragment tag to page tag to page nothing
after any fragment nothing nothing nothing
end of tag file restore tag to page tag to page
Save/restore means that the value of the variable from the page is saved on entry to tag and the restored afterwards. Consequently, any changes made inside the tag are not visible to the the page. The actions of the tag therefore apply only within the tag, which is suitable for the NESTED scope.
Tag to page means that the value of the variable inside the tag is copied to the page's copy of the variable. Consequently, for AT_END scope, the tag sees the initial value of the variable from the page, and then exports the value back to the page at the end. For the AT_BEGIN scope, the tag observes an uninitialized value for the variable, and exports it back to the page at the end.
Is that any clearer?

Dynamic attributes

The tag directive can include a dynamic-attributes attribute, which gives the name of a map which stores all of the tag attributes which are not explicitly declared. Here's an example of how it can be used. First, the tag file (tabular.tag), which shows how to receive the attributes via Java and using JSTL and expressions:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ tag import="java.util.*" %> <%@ tag dynamic-attributes="attributes" %> <%@ attribute name="title" %> <% out.println(jspContext.getAttribute("title")); out.println("<table border='1'>"); Map m = (Map) jspContext.getAttribute("attributes"); for (Iterator i = m.keySet().iterator(); i.hasNext(); ) { Object key = i.next(); Object val = m.get(key); out.println("<tr>"); out.println("<td>" + key + "</td>"); out.println("<td>" + val + "</td>"); out.println("</tr>"); } out.println("</table>"); %> ${title} <table border='1'> <c:forEach var="item" items="${attributes}"> <tr><td>${item.key}</td><td>${item.value}</td></tr> </c:forEach> </table>
And the jsp page:
<%@ taglib prefix="util" tagdir="/WEB-INF/tags" %> <html> <head> </head> <body> <util:tabular title="Dynamic Attributes!" name="Fred" wife="Wilma" friend="Barney" lover="Betty"/> </body> </html>
Which produces two copies of the following:
Dynamic Attributes!
wife Wilma
friend Barney
name Fred
lover Betty
to top

JSP 2.0 Tags

Introduction

The second new mechanism in JSP 2.0 is a simplified approach to writing tags using Java classes. It's based on the 'SimpleTag' interface, which is not even in the same class hierarchy as the tag classes from JSP 1.0. It also uses the 'JspFragment' class to represent sections of a JSP page. The implementation of tag files uses the JSP 2.0 tag approach; a JSP 2.0 tag is generated from the tag file.
The JSP 1.0 approach is similar to event-driven programming - the JSP code reacts to tag start, end and body events. The JSP 2.0 approach is more top-down: the tag class has a single method, doTag(), which is called when the tag is encountered, and the body is presented to the tag class as a JspFragment.
To evaluate the body, the JspFragment class provides an invoke() method. For iterating over the body, the tag class just calls invoke() in a loop. invoke() takes a writer as parameter; you can supply a writer if you want to capture the output, or pass null to have it write to the default stream.
Here's the SimpleTag interface:
public interface SimpleTag extends JspTag { public void doTag()throws JspException, java.io.IOException; public void setParent(JspTag parent); public JspTag getParent(); public void setJspContext(JspContext pc); public void setJspBody(JspFragment jspBody); }
JspTag was introduced in 2.0 as a base interface for Tag and SimpleTag. It defines no methods.
When implementing a simple tag, one typically derives from SimpleTagSupport. It provides getJspBody() and findAncestorWithClass() methods.
When the doTag() method is called, setters for all attributes, and the setJspBody() method (if necessary) will have been called. The entire processing of the tag therefore takes place in doTag().
The JspContext object replaces the page context object used in JSP 1.0 tags. The idea is that this object is not dependent on http servlet classes, making the approach more generic.
The body is available to the tag as a JspFragment, but it is also possible to define attributes as fragments. In this case the setter method must accept a JspFrament object, and the attribute must be declared as a fragment in the descriptor file. This approach is useful when the tag needs to display different template text depending on runtime conditions.

Example

Here's a rather complicated example. The idea is to build a table of primes, where the background color of each row indicates whether the prime is even, or of the forms 4k+1 or 4k+3. This is interesting (to some), since Fermat proved that all primes of the form 4k+1 can be written as the sum of two squares, while all primes of the form 4k+3 cannot be so written. That's interesting, isn't it? The JSP page, test-fragment.jsp, appears below. Note how all of the template text that builds the table is contained in the JSP page. The conditional parts, which define the background color of the row, depending on the prime in question, appear as attributes of the custom tag.
<%@ taglib prefix='asitl' uri='http://www.alethis.net/tags/asitl' %> <html> <head> <title>JSP Test Tag</title> </head> <body> <asitl:primes> <jsp:attribute name="count"> 10 </jsp:attribute> <jsp:attribute name="evens"> <tr bgcolor='lightgreen'><td>${index}</td><td>${value}</td></tr> </jsp:attribute> <jsp:attribute name="odds1"> <tr bgcolor='cyan'><td>${index}</td><td>${value}</td></tr> </jsp:attribute> <jsp:attribute name="odds3"> <tr bgcolor='pink'><td>${index}</td><td>${value}</td></tr> </jsp:attribute> <jsp:body> <table border='1' cellpadding='3'> <tr><th>Position</th><th>Prime</th><tr> ${content} </table> </jsp:body> </asitl:primes> </body> </html>
The output:
Position Prime
1 2
2 3
3 5
4 7
5 11
6 13
7 17
8 19
9 23
10 29
The descriptor file:
<?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/web-jsptaglibrary_2_0.xsd' version='2.0'> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <uri>http://www.alethis.net/tags/asitl</uri> ... <tag> <name>primes</name> <tag-class>net.alethis.tags.PrimesTag</tag-class> <body-content>scriptless</body-content> <attribute> <name>count</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>evens</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <fragment>true</fragment> </attribute> <attribute> <name>odds1</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <fragment>true</fragment> </attribute> <attribute> <name>odds3</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <fragment>true</fragment> </attribute> <variable> <name-given>index</name-given> <variable-class>java.lang.String</variable-class> <declare>true</declare> <scope>NESTED</scope> </variable> <variable> <name-given>value</name-given> <variable-class>java.lang.String</variable-class> <declare>true</declare> <scope>NESTED</scope> </variable> <variable> <name-given>content</name-given> <variable-class>java.lang.String</variable-class> <declare>true</declare> <scope>NESTED</scope> </variable> </tag> </taglib>
The implementation class, PrimesTag.java. Note how the output from the attribute fragments is captured, while the output from the body fragment is just sent to the standard output writer.
package net.alethis.tags; import java.io.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class PrimesTag extends SimpleTagSupport { private String countString; private JspFragment evensFragment; private JspFragment odds1Fragment; private JspFragment odds3Fragment; public void setCount(String s) { countString = s; } public void setEvens(JspFragment f) { evensFragment = f; } public void setOdds1(JspFragment f) { odds1Fragment = f; } public void setOdds3(JspFragment f) { odds3Fragment = f; } public void doTag() throws JspException { try { int count = Integer.parseInt(countString); StringWriter sw = new StringWriter(); int lastPrime = 0; int total = 0; for (int i = 1; total < count; i++) { int p = nextPrime(lastPrime); total++; getJspContext().setAttribute("index", String.valueOf(total)); getJspContext().setAttribute("value", String.valueOf(p)); if (p % 2 == 0) { evensFragment.invoke(sw); } else if (p % 4 == 1) { odds1Fragment.invoke(sw); } else if (p % 4 == 3) { odds3Fragment.invoke(sw); } lastPrime = p; } getJspContext().setAttribute("content", sw.toString()); getJspBody().invoke(null); } catch (Exception e) { throw new JspTagException("error in RepeatTag: " + e.getMessage()); } } private int nextPrime(int start) { for (int k = start + 1; ; k++) { if (k < 2) { continue; } boolean isPrime = true; int sqrt = (int) Math.sqrt(k); for (int m = 2; m <= sqrt; m++) { if (k % m == 0) { isPrime = false; break; } } if (isPrime) { return k; } } } }
The war file structure:
test-primes.jsp WEB-INF asitl.tld classes net alethis tags PrimesTag.class
to top