You are on page 1of 43

Mastering custom JSP tags

Skill Level: Introductory

Rick Hightower (rhightower@arc-mind.com)


J2EE Developer
ArcMind

01 Jun 2004

Interested in adding custom tags to your JavaServer Pages (JSP) applications? This
tutorial will show you how to use these tags to write custom actions that are similar to
those built in to JSP technology, such as jsp:useBean, jsp:getProperty, and
jsp:forward. Learn how you can extend the JSP syntax with your very own custom
actions that are specific to your domain's presentation logic.

Section 1. About this tutorial

Purpose of this tutorial


Are you interested in adding custom tags to your JavaServer Pages (JSP)
applications? This tutorial shows you how to use these tags to write custom actions
that are similar to those built in to JSP technology, such as jsp:useBean,
jsp:getProperty, and jsp:forward. Learn how you can extend the JSP syntax
with your very own custom actions that are specific to your domain's presentation
logic.

The ability to add custom tags to JSP applications will allow you to focus your efforts
on a document-centric approach to development. You can keep Java code out of
your JSP pages, which makes those pages easier to maintain. (I know from
experience, when too much Java code is put into a JSP page it can be a code
maintenance nightmare.) This tutorial will get you developing custom tags in no time.
And once you learn the benefits of JSP custom tag development, you may be
surprised that programmers don't use them more often.

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 1 of 43
developerWorks® ibm.com/developerWorks

In this tutorial, I'll cover the basics of using custom tags. You'll learn how to use
custom tags to create reusable presentation components and avoid introducing Java
scriptlets into your JSP pages.

Over the course of this tutorial, you'll:

• Define a JSP custom tag architecture.


• Explain simple tags.
• Define nested tags.
• Explain tags with BodyContent.
• Add attributes to tags.
• Add scriptlet variables to tags.
• Implement control flow with custom tags.
• Use Struts to simplify tag development.

Should I take this tutorial?


If you find yourself adding a lot of Java scriptlets to your JSP applications, then this
is the tutorial for you. After reading this tutorial, you should have the information you
need to keep Java code out of your JSP pages.

This tutorial assumes that you are familiar with the Java platform, JavaServer Pages
(JSP) technology, the MVC pattern, the Reflection API, Model 2, and, optionally, the
Struts framework. In addition, you'll need a good background in using custom tag
libraries to get the most out of this tutorial. (See Resources for more information.)

Section 2. Custom tag architecture: An introduction

Tag handlers
Before you can create a custom tag, you need to create a tag handler. A tag handler
is a Java object that performs the action of a custom tag. When you use a custom
tag, you import a tag library -- that is, a list of tag/tag handler pairs. You import a
library by declaring it in the Web application deployment descriptor, then importing it
into a JSP page using the taglib directive. If the JSP container encounters the use

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 2 of 43
ibm.com/developerWorks developerWorks®

of a custom tag at translation time, it looks up the appropriate tag handler by


inspecting the tag library descriptor (TLD) file. A TLD file is to custom tag handlers
what a Web deployment descriptor is to servlets.

At runtime, the servlet generated by the JSP page gets an instance of the tag
handler class corresponding to the tag used in the page. The generated servlet
initializes the tag handler with any attributes that you pass.

The tag handler implements life cycle methods. The generated servlet uses these
methods to inform the tag handler that the custom tag's action should start, stop, or
be repeated. The generated servlet calls these life cycle methods to perform the
functionality of the tag.

Types of tags
You can define two types of tags:

• javax.servlet.jsp.tagext.Tag
• javax.servlet.jsp.tagext.BodyTag
Tags that operate on their body -- that is, on the material between the opening and
closing tags -- must implement the BodyTag interface. In this tutorial, we'll refer to
these tags as body tags. We'll call tags that do not operate on their body simple
tags. Simple tags can implement the Tag interface, though they aren't required to do
so. Keep in mind that a tag that does not operate on its body still has a body;
however, its tag handler cannot read that body.

Section 3. Simple tags

An example of a simple tag


There are several custom tag libraries that ship with the Struts Framework (see
Resources for a link to more information on Struts). One of the tags from these
libraries allows you to create a link that supports rewriting URLs and encoding the
rewritten link with the jsessionid.

There's a problem, though: If you want to pass a list of request parameters (a query
string, for instance), you may have to create a Java scriptlet to do so. What a mess!
The following listing (search_results.jsp) illustrates a JSP page that by necessity

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 3 of 43
developerWorks® ibm.com/developerWorks

includes such a scriptlet. POOP.

<%@ taglib uri="struts-html" prefix="html" %>


<jsp:useBean class="java.util.HashMap" id="deleteParams" />
<%
deleteParams.put("id", cd.getId());
deleteParams.put("method","delete");
%>
<!-- Pass the map named deleteParams to html:link to generate
the request parameters-->
<html:link action="/deleteCD"
name="deleteParams">delete </html:link> </font></td>

The search_results.jsp creates a hashmap and passes that map two properties. In
the next few sections, we are going to create a custom tag that does this work
without Java code. Our tag will define a hashmap as follows:

<map:mapDefine id="deleteParams">
<map:mapEntry id="id" name="cd" property="id"/>
<map:mapEntry id="method" value="delete"/>
</map:mapDefine>
<!-- Pass the map named deleteParams to html:link to generate
the request parameters-->
<html:link action="/deleteCD"
name="deleteParams">delete </html:link> </font></td>

This will allow us to create small maps easily.

This example will demonstrate several key concepts, including working with nested
tags and defining scriptlet variables. First I'll explain how this simple tag works, then
you'll build on these concepts in later sections, and learn how to write variations of
this tag that work with their bodies and control execution flow.

Steps to building a simple tag


Let's create a tag that defines a HashMap scriptlet variable. In order to do this, you
will need to implement the tag handler interface (javax.servlet.jsp.tagext.Tag). Thus,

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 4 of 43
ibm.com/developerWorks developerWorks®

this first tag we will create will be a simple tag.

The tag will instantiate a map. A developer using the tag can specify the type of map
that will be instantiated -- a HashMap, TreeMap, FastHashMap, or FastTreeMap.
The FastHashMap and FastTreeMap are from the Jakarta Commons Collection
library (see Resources for a link). The developer will also be able to specify the
scope -- whether it be page, request, session, or application scope -- into which the
tag should be placed.

In order to build this simple tag, we will have to walk through the following steps:

1. Create a tag handler class that implements the Tag interface


(javax.servlet.jsp.tagext.Tag, to be exact).

2. Create a TLD file.

3. Create attributes in the tag handler Java class.

4. Define attributes in the TLD file that correspond to the attributes defined in
the tag handler Java class.

5. Declare scriptlet variables in the TLD file.

6. Implement the doStartTag() method. In the tag handler class, set a


value into the scriptlet variable based on the attributes.

If you're like me, you like to read the end of the book long before you get to it, so
check the Appendix for the complete listing of our tag handler class to see how this
process is going to end up.

Over the course of the next few sections, we will look at the implementation of the
MapDefineTag and see how we can get to this point.

Step 1: Create a tag handler that implements the Tag interface


In order to write a tag handler, you must implement the Tag interface. As noted
earlier, this interface is for simple tag handlers that do not manipulate their tag body.
As the J2EE API docs (see Resources for a link) put it: The Tag interface defines
the basic protocol between a tag handler and JSP page implementation class. It
defines the life cycle and the methods to be invoked at start and end tag.

The tag handler interface has the following methods:

Method Purpose
int doStartTag() throws JspException Process the start tag

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 5 of 43
developerWorks® ibm.com/developerWorks

int doEndTag() throws JspException Process the end tag


Tag getParent()/void setParent(Tag t) Get/set the parent of the tag
void setPageContext(PageContext pc) Setter method for the pageContext property
void release() Release any resources that were obtained

TagSupport

Now, you don't have to implement the Tag interface directly; instead, your
map-defining tag will subclass the TagSupport class. This class implements the Tag
interface with sensible default methods, thus making it easier to develop custom
tags. (See Resources for a link to TagSupport's API documentation.) For example,
the TagSupport class defines get/setParent() and setPageContext(), which
would be nearly the same for all tag handlers The get/setParent() methods
allow tags to be nested. The TagSupport class also defines a pageContext
instance variable (protected PageContext pageContext) that can be used by
subclasses; this variable is set by the setPageContext() method.

By default, TagSupport implements doStartTag() so that it returns the


SKIP_BODY constant, meaning that the tag body will not be evaluated. In addition,
the doEndTag() method by default returns EVAL_PAGE, which means that the JSP
runtime engine should evaluate the rest of the page. Lastly, TagSupport implements
release(), which sets the pageContext and parent to null.

The TagSupport class also implements the IterationTag interface and


implements doAfterBody() so that it returns SKIP_BODY. I'll explain this in more
detail later when I cover tags that iterate (see Control flow with custom tags ).

Without further ado, let's implement the Tag interface by subclassing TagSupport as
follows:

...
import javax.servlet.jsp.tagext.TagSupport;
...
public class MapDefineTag extends TagSupport {
...

Now that we have defined the tag handler, we'll need to add the mapping from the
handler to the tag in the TLD file. We'll tackle this in the next sections. Later, we'll fill
in the rest of the code for MapDefineTag.

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 6 of 43
ibm.com/developerWorks developerWorks®

Step 2: Create a TLD file


A TLD file fills the same role for custom tag handlers that a Web deployment
descriptor fills for servlets. The TLD file lists mappings from tag names to tag
handlers. Most of the data in the file is used at JSP page translation time. TLD files
are usually kept in the WEB-INF directory of a Web application and then declared in
the web.xml file. They usually end with a .tld extension.

A TLD file has a preamble, where we identify the version of JSP technology and tag
libraries that we are using. This preamble usually looks something like this:

<?xml version="1.0" encoding="UTF-8"?>


<!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>
<short-name>map</short-name>

Let's look at these tags in more detail:

• The root element the TLD file is taglib. A taglib describes a tag
library -- that is, a list of tag/tag handler pairs.
• The tlib-version and short-name elements are required in this
example, because we are using JSP version 1.2.
• The tlib-version element corresponds to the tag libraries version.
• The jsp-version corresponds to the version of JSP technology on
which our tag library depends.
• The short-name element defines a simple name for the tag library that
can be used by IDEs and other development tools.
• The taglib element contains many tag elements, one for each tag in
the tag library.
Because we just created our class, we will go ahead and declare that class in the
TLD file, as follows:

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 7 of 43
developerWorks® ibm.com/developerWorks

<taglib>
...
<tag>
<name>mapDefine</name>
<tag-class>trivera.tags.map.MapDefineTag</tag-class>
<body-content>JSP</body-content>
...

The tag element is used to map custom tags to their custom tag handlers. The tag
element in the listing above maps the custom tag mapDefine to the handler
trivera.tags.map.MapDefineTag. Thus, whenever the translation engine runs
across mapDefine, it will invoke trivera.tags.map.MapDefineTag.

Now that we have defined the tag in the TLD, we'll next define some attributes for
that tag in the tag handler class.

Step 3: Create attributes in the tag handler Java class


We want to specify three attributes for the mapDefine tag, as follows:

Attribute Description
id The name of the new scriptlet variable.
scope The scope where we will put the new scriptlet
variable.
type The type the new scriptlet variable will be
(HashMap, FastHashMap, TreeMap, or
FastTreeMap.) If type is set to hash, then a
HashMap will be created. If type is set to
fasthash, then a FastHashMap will be
created.

When we use our tag in a JSP page, it will look like this:

<map:mapDefine id="editParams" scope="session" type="hash">


...
</map:mapDefine>

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 8 of 43
ibm.com/developerWorks developerWorks®

This tag will create a HashMap called editParams in session scope.

In order to create attributes in a tag handler, you need to define corresponding


JavaBean properties. Thus, every attribute will have a corresponding setter method
in the tag handler, as follows:

public class MapDefineTag extends TagSupport {

...
private String type = FASTTREE;
private String id;
private String scope;

public void setType(String string) {


type = string;
}

public void setId(String string) {


id = string;
}

public void setScope(String string) {


scope = string;
}

The translation engine will set the properties of our tag with either hardcoded
configuration data or runtime expressions. We'll discuss this in more detail in Step 4:
Define attributes in the TLD file.

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 9 of 43
developerWorks® ibm.com/developerWorks

In Step 5: Implementing the doStartTag() method, we'll use these attributes in our
tag handler's doStartTag() method.

Step 4: Define attributes in the TLD file


Custom tag attributes are defined by declaring JavaBean properties, as we did in the
last section, and then declaring the attributes in the TLD file. The name of each
JavaBean property must match the name of the corresponding custom tag attribute.
Every attribute defined in the TLD must match a JavaBean property, though you can
have JavaBean properties that don't match up with tag attributes.

Here are the attribute declarations for MapDefineTag:

<tag>

<name>mapDefine</name>
<tag-class>trivera.tags.map.MapDefineTag</tag-class>
<body-content>JSP</body-content>

...
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
<description>The id attribute</description>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<description>The scope attribute</description>
</attribute>
<attribute>
<name>type</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 10 of 43
ibm.com/developerWorks developerWorks®

<description>
Specifies the type of map valid values are fasttree, fasthash, hash, tree
</description>
</attribute>
</tag>

The name element specifies the name of the attribute. The required element
specifies whether the attribute is required (the default value is false). The
rtexprvalue element indicates whether the attribute is hardcoded based on the
value at translation time or if it allows runtime scriptlet expressions.

Remember, the MapDefineTag class must define a JavaBean property for each
attribute described above; we took care of this in Step 3: Create attributes in the tag
handler Java class.

Step 5: Implementing the doStartTag() method


The doStartTag() method is called when the tag starts -- from the developer's
perspective, this happens when the engine runs into <map:mapDefine>. If
doStartTag() returns SKIP_BODY, then the body tag will not be processed. If it
returns an EVAL_BODY_INCLUDE, then the body will be processed.

The doStartTag() method of the MapDefine class does the following:

• Determines the type of map to create, based on the type attribute


• Determines where to put the new map object into scope, based on the
scope attribute
• Determines the name of the scope into which the new map object should
be placed, based on the id attribute
Let's look at this process in more detail. The MapDefine class checks to see if the
type attribute was set to FASTTREE, HASH, TREE, or FASTHASH. It then creates the
corresponding map, as follows:

/* String constants for the different types of maps we support */


public static final String FASTHASH = "FASTHASH";

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 11 of 43
developerWorks® ibm.com/developerWorks

public static final String FASTTREE = "FASTTREE";


public static final String HASH = "HASH";
public static final String TREE = "TREE";

/** The map we are going to create */


private Map map = null;

/** The member variable that holds the type attribute */


private String type = FASTTREE;
...

public int doStartTag() throws JspException {

/** Based on the type attribute, determines which type of Map to create */
if (type.equalsIgnoreCase(FASTTREE)) {
map = new FastTreeMap();
} else if (type.equalsIgnoreCase(HASH)) {
map = new HashMap();
} else if (type.equalsIgnoreCase(TREE)) {
map = new TreeMap();
} else if (type.equalsIgnoreCase(FASTHASH)) {
map = new FastHashMap();
}

Next, the id and scope attributes are used to set the hashmap into a given scope
with a given name:

private String id;


private String scope;

public int doStartTag() throws JspException {

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 12 of 43
ibm.com/developerWorks developerWorks®

...
if (scope == null){
pageContext.setAttribute(id, map);
}else if("page".equalsIgnoreCase(scope)){
pageContext.setAttribute(id, map);
}else if("request".equalsIgnoreCase(scope)){
pageContext.getRequest().setAttribute(id, map);
}else if("session".equalsIgnoreCase(scope)){
pageContext.getSession().setAttribute(id, map);
}else if("application".equalsIgnoreCase(scope)){
pageContext.getServletContext().setAttribute(id, map);

return EVAL_BODY_INCLUDE;
}

If the scope attribute is null, then the map will be put into page scope. Otherwise,
the parameter will be put into the scope corresponding to the scope name passed
via the scope attribute.

So far, we have a pretty simple tag that has three attributes: id, scope, and type.
We've put the map into a scope with a given name. One thing we haven't done yet,
however, is declare a scriptlet variable. In order to create to do so, we need to add
one more entry to the TLD, and that's what we'll do in the next section.

Step 6: Declare scriptlet variables


To understand scriptlet variables, you have to understand the role of the TLD file.
This file is largely a repository of metadata that tags use while a JSP page is being
translated into a servlet. The scriptlet variables become local variables in the
generated servlet. In order for the JSP translation engine to know the types that
these variables should be declared, you need to add entries like the following to the
TLD file:

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 13 of 43
developerWorks® ibm.com/developerWorks

<variable>
<name-from-attribute>id</name-from-attribute>
<variable-class>java.util.Map</variable-class>
<scope>AT_BEGIN</scope>
</variable>

The above code snippet is placed in the TLD file after the body-content element
and before the attribute elements. Under the variable element, we have
declared three sub-elements: name-from-attribute, variable-class, and
scope. name-from-attribute specifies that the value of the id attribute is the
name of the scriptlet variable that the translation engine will define.
variable-class is the class type of the variable that the translation will define.
scope specifies when the variable will be available: it can be nested inside of the
body of the tag (NESTED), after the end of the tag (AT_END), or from the beginning of
the tag (AT_BEGIN). For scope, we opted for AT_BEGIN, which means that the
variable will be available from the start of the tag to the end of the current JSP page.

Now you have a sense of how a simple custom tag is put together. In the next
section, we'll look at our tag's life cycle methods to understand what happens as our
JSP page is actually run.

Life cycle overview of a simple tag


If you are like I was when I first started with tags, you probably find it a little difficult
to map the translation engine life cycle on to what actually happens at runtime. Like
me, you've probably seen a lot of diagrams that try to clear these concepts up; these
diagrams probably look a lot like the one below.

Diagram

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 14 of 43
ibm.com/developerWorks developerWorks®

The diagrams did not make much sense to me, but the generated code did, so we'll
take a look at that code in this section. (The diagrams make sense to me now, but I
found the code much more revealing; you might want to examine that diagram again
when we're done with this section.)

As you may recall, JSP pages are really servlets in disguise. Your JSP files are

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 15 of 43
developerWorks® ibm.com/developerWorks

translated into servlets before they are used. Here is a small JSP page, called
testMapDefine.jsp, that we will use to demonstrate and test MapDefineTag, the tag
we developed in the previous section:

<%@taglib uri="map" prefix="map"%>


<html>
<head><title>Test Map Define</title></head>
<body>

<map:mapDefine id="employee">
<br />
The employee is <%=employee%> <br />
<%
employee.put("firstName", "Kiley");
employee.put("lastName", "Hightower");
employee.put("age", new Integer(33));
employee.put("salary", new Float(22.22));
%>
The employee is <%=employee%> <br />
</map:mapDefine>

</body>
</html>

Notice that this page imports a custom tag with the URI of map. It does this because
we declared the TLD in the web.xml file as follows:

<web-app>
...
<taglib>
<taglib-uri>map</taglib-uri>
<taglib-location>/WEB-INF/tlds/map.tld</taglib-location>
</taglib>

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 16 of 43
ibm.com/developerWorks developerWorks®

...

There are other ways to import tags. I find this method the most useful, as it allows
you to assign a short name for the URI, and short names are easy to remember.
Notice that the taglib is described in the context of its TLD file. Our map.tld file,
located in the WEB-INF directory, looks like this:

<?xml version="1.0" encoding="UTF-8"?>


<!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>
<short-name>map</short-name>

<tag>
<name>mapDefine</name>
<tag-class>trivera.tags.map.MapDefineTag</tag-class>
<body-content>JSP</body-content>
...

When the JSP translator runs into the use of the custom tag mapDefine, it will look
up the TLD file based on the taglib directive and the TLD file specified in the
web.xml file. It then adds code like the following to the generated servlet:

public class _testmapdefine__jsp extends ...JavaPage{

public void _jspService(HttpServletRequest request,


HttpServletResponse response) throws...{

trivera.tags.map.MapDefineTag tag0 = null;

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 17 of 43
developerWorks® ibm.com/developerWorks

java.util.Map employee = null;


try {
...
if (tag0 == null) {
tag0 = new trivera.tags.map.MapDefineTag();
tag0.setPageContext(pageContext);
tag0.setParent((javax.servlet.jsp.tagext.Tag) null);
tag0.setId("employee");
}

int includeBody = tag0.doStartTag();


if (includeBody != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {
employee = (java.util.Map)pageContext.findAttribute("employee");

out.print("<br /> \n The employee is "+ (employee) +"<br /> \n");

employee.put("firstName", "Kiley");
employee.put("lastName", "Hightower");
employee.put("age", new Integer(33));
employee.put("salary", new Float(22.22));

out.print("<br /> \n The employee is "+ (employee) +"<br /> \n");


}
employee = (java.util.Map)pageContext.findAttribute("employee");
...
} catch (java.lang.Throwable _jsp_e) {
pageContext.handlePageException(_jsp_e);
...
}
...
}

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 18 of 43
ibm.com/developerWorks developerWorks®

The generated JSP servlet declares a local variable of type


trivera.tags.map.MapDefineTag called tag0. It then creates a instance of a
tag, sets the page context, sets the parent to null, and sets the ID to employee.
Next, the generated servlet calls the doStartTag() method. It checks to see if the
return type is set to Tag.SKIP_BODY. If it is, the container does not evaluate the
body of the tag -- the if block, in this case. If it returns EVAL_BODY_INCLUDE, as
our tag will, the container will process the body. You can use this technique to
conditionally include the body of a tag -- that is, to control flow.

Notice that the generated servlet has a local variable called employee, based on
the id attribute of the tag being set to employee. Thus, the translation engine
defines a local variable called employee at translation time, not at runtime. This is a
concept that confuses a lot of novices.

Section 4. Nested tags

Understanding nested tags


Our previous JSP page example used JSP scriptlets to add items to an employee
map. It would be nice if we could do this with another tag instead. Let's define a
nested tag called MapEntryTag that will get its parent tag by calling getParent()
and casting it to a MapDefineTag.

The MapDefineTag defines a getMap() method that returns the newly created
map. The nested MapEntryTag in doEndTag() uses MapDefineTag's getMap()
method to add a value to the map, as follows:

public class MapEntryTag extends TagSupport {


String type = "java.lang.String";
String id;
String value;
String name;
String property;
String scope;

public int doEndTag() throws JspException {

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 19 of 43
developerWorks® ibm.com/developerWorks

/* Grab the MapDefineTag using the getParent


method and cast it to a MapDefineTag.*/
MapDefineTag mapDef = (MapDefineTag) this.getParent();
Object objectValue = null;

...
/* Instantiate a new String, Integer or Float based on the type. */
if (type.equals("java.lang.String")) {
objectValue = value;
} else if (type.equals("java.lang.Integer")) {
Integer intValue = Integer.valueOf(value);
objectValue = intValue;
} else if (type.equals("java.lang.Float")) {
Float floatValue = Float.valueOf(value);
objectValue = floatValue;
}

/* Put the new entry into the map. */


mapDef.getMap().put(id,objectValue);
return EVAL_PAGE;

Based on the above code, and assuming that we add the necessary entries to the
TLD file, we could use our tag like this:

<%@taglib uri="map" prefix="map"%>


<html>
<head><title>Test Map Define Entry</title></head>
<body>

<map:mapDefine id="employee">

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 20 of 43
ibm.com/developerWorks developerWorks®

<map:mapEntry id="firstName" value="Jennifer"/>


<map:mapEntry id="lastName" value="Wirth"/>
<map:mapEntry id="age" value="33" type="java.lang.Integer"/>
<map:mapEntry id="salary" value="22.22" type="java.lang.Float"/>
</map:mapDefine>

The employee is set as <%=employee%> <br />

The above listing would define an employee map with three entries apiece for
firstName, lastName, age, and salary, of types String, String, Integer,
and Float, respectively.

Using Reflection to extract bean properties as values


Developers often use Java Reflection to improve custom tag code. On this section,
we're going to rewrite MapEntryTag with Reflection so that it can use any bean
property in any scope to define an entry into the map. For example, imagine that we
have a bean that looks like this:

public class Test {

String test="Jenny";
public String getTest() {
return test;
}

public void setTest(String string) {


test = string;
}

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 21 of 43
developerWorks® ibm.com/developerWorks

Our rewritten tag can use the bean property as an entry into the map, like this:

<jsp:useBean id="bean" class="trivera.tags.map.Test"/>

<map:mapDefine id="employee2">

<map:mapEntry id="firstName" name="bean" property="test"/>


<map:mapEntry id="age" value="33" type="java.lang.Integer"/>
<map:mapEntry id="salary" value="22.22" type="java.lang.Float"/>
<map:mapEntry id="properties" name="properties" />
</map:mapDefine>

Notice that the firstName entry is now defined with the bean's test property.

To accomplish this, we need to add Reflection in our custom tag, like so (follow the
comments in the code to see how things were changed):

public class MapEntryTag extends TagSupport {...

/* All of these have corresponding getters and setter method */


String type = "java.lang.String"; //Holder for the type attribute
String id; //Holder for the id attribute
String value; //Holder for value attribute
String name; //Holder for name attribute
String property; //Holder for property attribute
String scope; //Holder for scope attribute

public int doEndTag() throws JspException {


MapDefineTag mapDef = (MapDefineTag) this.getParent();
Object objectValue = null;

/* Check to see if the value property is set,

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 22 of 43
ibm.com/developerWorks developerWorks®

if it is then this is a simple entry */


if (value !=null){

if (type.equals("java.lang.String")) {
objectValue = value;
} else if (type.equals("java.lang.Integer")) {
Integer intValue = Integer.valueOf(value);
objectValue = intValue;
} else if (type.equals("java.lang.Float")) {
Float floatValue = Float.valueOf(value);
objectValue = floatValue;
}
/* If it is not a simple entry,
then use reflection to get the property from the bean */
}else {

Object bean =null;

if (scope == null){
bean = pageContext.findAttribute(name);
}else if("page".equalsIgnoreCase(scope)){
bean = pageContext.getAttribute(name);
}else if("request".equalsIgnoreCase(scope)){
bean = pageContext.getRequest().getAttribute(name);
}else if("session".equalsIgnoreCase(scope)){
bean = pageContext.getSession().getAttribute(name);
}else if("application".equalsIgnoreCase(scope)){
bean = pageContext.getServletContext().getAttribute(name);
}
/* If the property attribute is null,
then just use the bean as the entry*/
if (property==null){
objectValue = bean;
mapDef.getMap().put(id,bean);

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 23 of 43
developerWorks® ibm.com/developerWorks

/* If the property attribute is set,


then use reflection to read the property */
}else {

try{
String propertyMethod = "get" +
property.substring(0,1).toUpperCase() +
property.substring(1, property.length());

Method prop = bean.getClass()


.getMethod(propertyMethod,new Class[]{});
objectValue = prop.invoke(bean, new Object[]{});
}catch(Exception e){
throw new RuntimeException(e);
}
}

mapDef.getMap().put(id,objectValue);
return EVAL_PAGE;
}

This seems like a lot of work just to achieve some functionality that a lot of tags need
to do anyway. Fortunately for us, there is a library that makes this type of
development easier. See how this library -- Struts -- can help in the next section.

Using Struts to simplify custom tag development


An in-depth discussion of Struts is beyond the scope of this tutorial. (Check out
Resources for more information on this framework.) If you're already familiar with the
framework, though, you can use your knowledge to aid your custom tag
development.

Instead of using Reflection to achieve the results we saw on the last section, you

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 24 of 43
ibm.com/developerWorks developerWorks®

could use Struts's RequestUtils, as follows:

public class MapEntryTag extends TagSupport {...

private String type = "java.lang.String";


...
public int doEndTag() throws JspException {
MapDefineTag mapDef = (MapDefineTag) this.getParent();
Object objectValue = null;

if (value !=null){

if (type.equals("java.lang.String")) {
objectValue = value;
} else if (type.equals("java.lang.Integer")) {
Integer intValue = Integer.valueOf(value);
objectValue = intValue;
} else if (type.equals("java.lang.Float")) {
Float floatValue = Float.valueOf(value);
objectValue = floatValue;
}
}else {
/** THIS USED TO BE 30 LINES OF CODE */
objectValue = RequestUtils.lookup(pageContext, name, property, scope);
}

mapDef.getMap().put(id,objectValue);
return EVAL_PAGE;
}

As you can see, the line objectValue =

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 25 of 43
developerWorks® ibm.com/developerWorks

RequestUtils.lookup(pageContext, name, property, scope);


replaces 30 lines of Reflection-based code! Struts ships with a lot of utilities that
make custom tag development easier.

Section 5. Body tags

Introduction
You can write tag handlers that manipulate their body content. Remember, a tag's
body content is the data that appears in the JSP page between the opening and
closing tag of the custom tag. Tags that manipulate their bodies are called body
tags. Body tag handlers are more complex to write than handlers for simple tags.

Note: Remember, simple tags can have bodies too. The only difference is that
simple tags can not read or manipulate their bodies.

To write a body tag handler, you need to implement the BodyTag interface.
BodyTag implements all the methods implemented by Tag (see Step 1: Create a
tag handler that implements the Tag interface for the details), along with two extra
methods that deal with body content:

Method Purpose
void setBodyContent(BodyContent b) Setter method for the bodyContent property.
void doInitBody() Prepares for evaluation of body. Called once per
tag invocation, after a new BodyContent is
obtained and set via setBodyContent(). Not
invoked if there is no body content requested
because doStartTag() has returned
EVAL_BODY_BUFFERED.

Just as the Tag interface has the TagSupport class, the BodyTag interface has the
BodyTagSupport class. Thus, your body tag handlers only need to override the
methods that they will use. The BodyTagSupport class subclasses TagSupport
and implements the BodyTag interface. This makes it easier to write body tag
handlers. BodyTagSupport defines get/setBodyContent() and a protected
bodyContent instance variable.

The BodyTagSupport class redefines the doStartTag() life cycle method to


return EVAL_BODY_BUFFERED. By returning EVAL_BODY_BUFFERED,
doStartTag() requests the creation of a new buffer -- that is, a BodyContent.

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 26 of 43
ibm.com/developerWorks developerWorks®

BodyContent is a buffer that holds the runtime results of the evaluation of the
body. BodyContent subclasses JspWriter and acts as the implicit out for the
body of the tag. Thus, the JSP container creates an instance of BodyContent, and
as it processes the body content of the tag, it writes to this instance instead to the
root JspWriter. Thus, when you use the implicit object out inside of a tag, you are
really using a BodyContent object, not the JspWriter. (The JspWriter is the
implicit out for the page.)

You can get the evaluated body as a String from BodyContent. BodyContents
are created at runtime by the container calling the pushBody() and popBody()
methods of the pageContext for the page. (pushBody() is called only if
doStartTag() returns EVAL_BODY_BUFFERED.) Thus, BodyContents are in a
nested structure of JspWriters and BodyContents. (The outer out can be
another BodyContent object, because a BodyContent is a JspWriter.) The
BodyContent is given to the body tag handler through the setBodyContent()
method. The body tag handler is passed a BodyContent instance (via
setBodyContent()) and can decide what to do with it. It may process it further,
discard it, send it to the browser, etc.

Enough background; let's look at the code! We'll look at a simple body tag example
in the next few sections.

Example: The map tag


The developer of a custom tag can decide what to do with the body of that tag. For
example, you could write a tag that executes a SQL statement; the body of the tag
would be the SQL statement to be executed.

Keeping with our hashmap theme, we will write a tag that parses a string that looks
like this:

{
firstName=Jennifer,
lastName=Wirth,
age=25
}

Essentially, our tag will read this kind of string and convert it to a Map
(java.util.Map). We will call our new tag map. Here is an example of our new tag
in action:

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 27 of 43
developerWorks® ibm.com/developerWorks

<%@taglib uri="map" prefix="map"%>


<html>
<head><title>Test Map JSP</title></head>
<body>

<map:map id="employee">
{
firstName=Jennifer,
lastName=Jones,

age=25
}
</map:map>

<br />
The employee map is <%=employee%> <br />

</html>

The code above creates a map called employee with three entries: firstName,
lastName, and age.

Implementing the tag handler


MapParseTag is a tag handler for the map tag. It defines a map based on the string
passed to the body. In the doAfterBody() method, MapParseTag grabs the body
content as a string using body.getString(). It then clears the body content using
body.clearBody().

public class MapParseTag extends BodyTagSupport {

private String id;


private Map map;

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 28 of 43
ibm.com/developerWorks developerWorks®

public int doStartTag() throws JspException {


map=new FastTreeMap();
return EVAL_BODY_BUFFERED;
}

public int doAfterBody() throws JspException {


/* Grab the body content */
BodyContent body = this.getBodyContent();

/* Get the body content as a String */


String content = body.getString();

/* Clear the body */


body.clearBody();

/* Parse the map */


int start = content.indexOf("{");
int end = content.lastIndexOf("}");
content = content.substring(start+1, end);

/* Parse the entries in the map */


StringTokenizer token = new StringTokenizer(content,"=;, \t\r\n");
while(token.hasMoreTokens()){

String key = token.nextToken();


String value = token.nextToken();
map.put(key,value);
}

this.pageContext.setAttribute(id,map);
return SKIP_BODY;
}

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 29 of 43
developerWorks® ibm.com/developerWorks

Simple enough! Now that we have covered tags that work on their bodies, let's cover
tags that implement execution flow in detail.

Section 6. Control flow with custom tags

Overview
At times, you may want to conditionally invoke the body of a method based on some
application-specific display logic. This can be done by returning a value from
doStartTag(): SKIP_BODY to skip the body of the tag, or EVAL_BODY to evaluate
it.

Iteration tags need to implement the IterationTag interface. The


doAfterBody() method is invoked by the container to determine whether to
reevaluate the body or not. This method returns EVAL_BODY_AGAIN to signal that
the container should keep evaluating the body. The doAfterBody() method
returns SKIP_BODY to signify that the iteration action is over. Both the TagSupport
class and the BodyTagSupport class implement the IterationTag interface.

Keeping with our map theme, in the next few sections we'll create a custom tag
handler that iterates over a collection of maps and prints out their values.

Control flow example


Before we go into the implementation of the custom tag handler, let's take a look at
our example tag in action. First, let's define two maps using the map tag from the last
example:

<%@taglib uri="map" prefix="map"%>


<html>
<head><title>Test Map JSP</title></head>
<body>

<map:map id="employee1">

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 30 of 43
ibm.com/developerWorks developerWorks®

{
firstName=Jennifer,
lastName=Wirth,
age=33
}
</map:map>

<map:map id="employee2">
{
firstName=Kiley,
lastName=McKeon,
age=27
}
</map:map>

Next, let's create a collection called list and add the two maps (employee1 and
employee2) defined above to it.

<jsp:useBean id="list" class="java.util.ArrayList" />


<%

list.add(employee1);
list.add(employee2);
%>

Now, here is how we would use a custom tag to iterate over our collection:

<map:printMaps name="list">

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 31 of 43
developerWorks® ibm.com/developerWorks

<table border="1">
<tr>
<td>First Name</td>
<td> ${firstName} </td>
<tr>
<td> Last Name </td>
<td> ${lastName} </td>
<tr>
<td>Age</td>
<td> ${age} </td>
</table>

</map:printMaps>

The map:printMaps tag iterates over the list collection. On each iteration, it
uses its body, searching for keys into the map for the current iteration by searching
for substrings that start with ${key}. It parses the key out of the ${} string and
replaces it with the value of the that key from the map -- that is, with
map.get(key).

Implementing the doStartTag() method


The doStartTag() method grabs the collection out of scope, then grabs the
iterator from the collection. If the iterator does not have any items in it
(iter.hasNext()), the doStartTag() method returns SKIP_BODY, thus
implementing a logical if. In this case, that if boils down to, "If the collection is
empty, skip the body evaluation." The iterator then grabs the first map out of the
collection.

public class MapPrintMapsTag extends BodyTagSupport {

private String name;

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 32 of 43
ibm.com/developerWorks developerWorks®

private Iterator iter;


private Map map;
private String scope;

public int doStartTag() throws JspException {


Collection collection = null;

/* Grab the collection out of scope using the scope attribute. */


if (scope == null){
collection = (Collection) pageContext.findAttribute(name);
}else if("page".equalsIgnoreCase(scope)){
collection = (Collection) pageContext.getAttribute(name);
}else if("request".equalsIgnoreCase(scope)){
collection = (Collection) pageContext.getRequest().getAttribute(name);
}else if("session".equalsIgnoreCase(scope)){
collection = (Collection) pageContext.getSession().getAttribute(name);
}else if("application".equalsIgnoreCase(scope)){
collection = (Collection) pageContext.getServletContext().getAttribute(name);
}

/* Get the iterator from the collection. */


iter = collection.iterator();

/* If the collection is empty skip the body evaluation. */


if (iter.hasNext()==false) return SKIP_BODY;

/* Grab the first map out of the collection. */


map = (Map)iter.next();
return EVAL_BODY_BUFFERED;
}

Implementing the doAfterBody() method

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 33 of 43
developerWorks® ibm.com/developerWorks

The doAfterBody() implements the iteration by returning SKIP_BODY if there are


no items left to iterate. If there are items left, it will return EVAL_BODY_AGAIN. As
long as doAfterBody() returns EVAL_BODY_AGAIN, the container will keep
evaluating the body, as follows:

public int doAfterBody() throws JspException {

/** Process body */


...

/** Write the processed buffer to the previous out */


...

if (iter.hasNext() == false) {
return SKIP_BODY;
} else {

map = (Map) iter.next();


return EVAL_BODY_AGAIN;
}
}

The doAfterBody() method grabs the body using getBodyContent(), then


uses the getString() method of the body content to get the string version of the
content. It next clears the body content buffer, and uses a StringTokenizer to
look for strings that start with ${. It also creates a StringBuffer called buffer.
As it iterates over the strings from the tokenizer, it appends them to the buffer.

If the string from the tokenizer starts with ${, doAfterBody(), it then extracts the
key from the string, and uses the key to look up the value in the map. It converts the
value from the map into a string by calling the toString() method of the value
object and appending the results to the buffer. The following listing illustrates how
this process works.

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 34 of 43
ibm.com/developerWorks developerWorks®

/** Process body */

/* Get and clear the body */


BodyContent body = this.getBodyContent();
String content = body.getString();
body.clearBody();

/* Process the body with a String tokenizer */


StringTokenizer token = new StringTokenizer(content);
/* Create an output buffer of the processed body */
StringBuffer buffer = new StringBuffer(content.length() * 2);

/* Iterate over the strings from the tokenizer


and put them into the output buffer. */
while (token.hasMoreTokens()) {
String tok = token.nextToken();

/* See if the String contains the special substring "${" */


if (tok.startsWith("${")) {

/* Parse the key out of the string */


String key = tok.substring(2, tok.length() - 1);

/* Use the key to look up the object in the map */


Object value = (String) map.get(key);
String svalue = tok;

/* If the value is not null,


get the value's string representation */
if (value != null) {
svalue = value.toString();
}

/* Add the string representation of the value


to the output buffer */

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 35 of 43
developerWorks® ibm.com/developerWorks

buffer.append(svalue + " ");


} else {
buffer.append(tok + " ");
}
}

Once the doAfterBody() method constructs the output buffer, it outputs it to the
previous out, like so:

try {
this.getPreviousOut().print(buffer.toString());
} catch (IOException e) {
throw new JspException(e);
}

Thus, each iteration processes the body and outputs it to the previous out. If the
previous out is the root JspWriter, then it will be written to the browser.

Section 7. Wrap-up and resources

Summary
With the completion of this tutorial, you now have a deeper understanding of custom
tags. If you completed this tutorial, and you are new to custom tags, then you should
be ready to start using this powerful tool on your current project. You will no longer
accept mediocre Java scriptlets strewn about your JSP pages in an unmaintainable
and unreusable manner. And even if you're not planning on creating your own
custom tags, this tutorial will help you understand custom tags that you might
encounter in code written by other developers.

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 36 of 43
ibm.com/developerWorks developerWorks®

Section 8. Appendix

Complete listing of tag handler class

package trivera.tags.map;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

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

import org.apache.commons.collections.FastHashMap;
import org.apache.commons.collections.FastTreeMap;

/**
*
* <p>
* Created to demonstrate the creation of custom tag handlers.
* Created by Rick Hightower from Trivera Technologies.
* </p>
* @jsp.tag name="mapDefine"
* body-content="JSP"
*
* @jsp.variable name-from-attribute="id" class="java.util.Map"
* scope="AT_BEGIN"
*/
public class MapDefineTag extends TagSupport {

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 37 of 43
developerWorks® ibm.com/developerWorks

public static final String FASTHASH = "FASTHASH";


public static final String FASTTREE = "FASTTREE";
public static final String HASH = "HASH";
public static final String TREE = "TREE";
private Map map = null;
private String type = FASTTREE;
private String id;
private String scope;

/* (non-Javadoc)
* @see javax.servlet.jsp.tagext.Tag#doStartTag()
*/
public int doStartTag() throws JspException {

if (type.equalsIgnoreCase(FASTTREE)) {
map = new FastTreeMap();
} else if (type.equalsIgnoreCase(HASH)) {
map = new HashMap();
} else if (type.equalsIgnoreCase(TREE)) {
map = new TreeMap();
} else if (type.equalsIgnoreCase(FASTHASH)) {
map = new FastHashMap();
}

if (scope == null){
pageContext.setAttribute(id, map);
}else if("page".equalsIgnoreCase(scope)){
pageContext.setAttribute(id, map);
}else if("request".equalsIgnoreCase(scope)){
pageContext.getRequest().setAttribute(id, map);
}else if("session".equalsIgnoreCase(scope)){
pageContext.getSession().setAttribute(id, map);
}else if("application".equalsIgnoreCase(scope)){

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 38 of 43
ibm.com/developerWorks developerWorks®

pageContext.getServletContext().setAttribute(id, map);
}

return EVAL_BODY_INCLUDE;
}

/** Getter for property type.


* @return Value of property type.
* @jsp.attribute
* required="false"
* rtexprvalue="false"
* description="Specifies the type of map
* valid values are fasttree, fasthash, hash, tree"
*/
public String getType() {
return type;
}

/**
* @param string
*/
public void setType(String string) {
type = string;
}

/** Getter for property id.


* @return Value of property id.
* @jsp.attribute required="true"
* rtexprvalue="false"
* description="The id attribute"
*/
public String getId() {
return id;
}

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 39 of 43
developerWorks® ibm.com/developerWorks

/**
* @param string
*/
public void setId(String string) {
id = string;
}

public Map getMap(){


return map;
}

public void release() {


super.release();
map = null;
type = FASTTREE;
}

/** Getter for property scope.


* @return Value of property scope.
* @jsp.attribute required="false"
* rtexprvalue="false"
* description="The scope attribute"
*/
public String getScope() {
return scope;
}

/**
* @param string
*/
public void setScope(String string) {
scope = string;
}

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 40 of 43
ibm.com/developerWorks developerWorks®

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 41 of 43
developerWorks® ibm.com/developerWorks

Resources
Learn
• Check out the J2EE API documentation for Tag and TagSupport.
• If you are looking for an application server to run these examples on, try IBM
WebSphere Express.
• Need to get up to speed on JSP custom tags, servlets, filters and more? Try this
online course from IBM: Servlet and JSP Development using WebSphere
Studio V5.
• For more on this topic, read "Take control of your JSP pages with custom tags,"
Jeff K. Wilson (developerWorks, January 2002).
• For more on taglibs, check out "JSP best practices: Intro to taglibs," Brett
McLaughlin (developerWorks, July 2003).
• For more on Struts, check out the Struts home page at the Apache Foundation.
Get products and technologies
• You can find the Common Collection Library at the Commons Collections Web
page.
Discuss
• Participate in the discussion forum for this content.

About the author


Rick Hightower
Rick Hightower is a J2EE developer and consultant who enjoys working
with J2EE, Ant, Hibernate, Struts, IBM's ETTK, and XDoclet. Rick is the
former CTO of Trivera Technologies, a global training, mentoring, and
consulting company focusing on enterprise development. He is a
regular contributor to IBM developerWorks, and has written more than
10 developerWorks tutorials on subjects ranging from EJB technology
to Web services to XDoclet. Rick just cofounded another company,
called ArcMind, which specializes in agile methods, and
Struts/JavaServer Faces development, consulting, and mentoring.
While working at eBlox, Rick and the eBlox team used Struts long
before the 1.0 release to build two frameworks and an ASP (application
service provider) for e-commerce sites. This framework currently
powers more than 2,000 online storefronts. Rick recently finished a
book called Professional Jakarta Struts. When not traveling around the

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 42 of 43
ibm.com/developerWorks developerWorks®

country consulting on J2EE and Struts projects or speaking at


conferences about J2EE and extreme programming, Rick enjoys
drinking coffee at an all-night coffee shop, writing about Struts, J2EE,
and other topics, and writing about himself in the third person.

Mastering custom JSP tags Trademarks


© Copyright IBM Corporation 2004. All rights reserved. Page 43 of 43

You might also like