HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 
Writing Custom JSP Tag Libraries, by Adam Chace

Server-side Java continues to gain ground as the technology of choice for powering dynamic Web sites, but the goal of using Java to separate presentation from business logic has been a tough one to achieve.

JavaServer Pages 1.1 addresses that goal with the introduction of custom JSP tag libraries. Java developers can now embed complex logic into middle-tier objects while exposing only simple, easy-to-use tags to the presentation layer. This frees Java developers to do what they do best while enabling presentation developers to focus on building good UIs.

If you're already using JavaBeans and the bean tags introduced in JSP 1.0, you know that these tags still require tag users to have some basic understanding of Java. Custom JSP libraries can abstract the implementation away completely so page designers don't even know what language is used behind the scenes. Even better, unlike bean tags, custom tags can inspect and modify the content within the tag's body. For example, a custom JSP tag could be used to translate content from HTML to WML or to apply formatting to some text.

Getting Started
Custom tags are made up of two components: the Tag Handler class and an XML file called a Tag Library Descriptor. The Tag Handler class contains the actual Java code executed during a page request. The Tag Library Descriptor (.tld file) contains the attributes for all tags in a particular tag library. The JSP engine uses these attributes to decide how to handle the tags at runtime.

Tags can come in two basic types: those with a body and those without. For my first tag I'll build a basic tag that has no body and simply prints a line of text onto the page. The first step is to create the Tag Handler class. To do this I define a new class that will implement the javax.servlet.jsp.tagext.Tag interface. To make things easier, you can also extend a class called TagSupport, which defines default methods for the Tag interface.

Listing 1 shows that I've implemented only one method of Tag, doStartTag(). This method is called whenever the JSP engine encounters an occurrence of my custom tag. The first thing I did in this method was to get an instance of JspWriter, which is a specialized version of java.io.Writer that can be used to write content to the page. The JspWriter is retrieved from pageContext, which is an instance of the PageContext class. PageContext is an abstract class implemented by the JSP engine vendor and provides access to all the namespaces and attributes of a particular JSP page. For now, I just need to use it to get the JspWriter to write out to the page.

You'll note that doStartTag must return an int value. The JSP engine uses this return value to determine how to process the remainder of the page. For tags like SimpleTag that implement the Tag interface, only two return values are valid:

  • SKIP_BODY: Instructs the JSP engine to ignore the body (if one exists) of this tag and not return it to the client
  • EVAL_BODY_INCLUDE: Instructs the JSP engine to evaluate and include the content of this tag's body and return it to the client
Since SimpleTag won't have a body at all, its doStartTag method simply returns SKIP_BODY. If I wanted to allow users of the tag to place some content between its start and end tags (and have that content interpreted and sent to the client), I could return EVAL_BODY_INCLUDE instead.

That's it for the TagHandler class. Now I need to write a TLD for this tag that will provide the JSP engine with the information it needs to use it. For SimpleTag this file will include the standard heading for a tag library that looks like this:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"web-jsptaglib_1_1.dtd">

<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>

This header is followed by the declaration of each individual tag. Here is the definition of our SimpleTag, with an explanation of what each attribute means:

<tag>
<name>simple</name>
This is the name that the tag will be referred to as in a JSP.
<tagclass>com.jdj.SimpleTag</tagclass>
This is the fully qualified class name of our SimpleTag class.
<bodycontent>empty</bodycontent>
This is where we indicate what type of body the tag will have. A value of empty means the tag will always appear as <tag/> (without a body). A value of JSP indicates the body can be interpreted as JSP. A third (and less used) possibility here is tagdependent, which means the tag will interpret the body entirely itself.
</tag>
Don't forget to close the tag library tag with </taglib>. I'll save the file as "jdj.tld".

Using My Tag
I now have a TagHandler class and a tld file and I'm ready to use the tag. The last step in the process is to write a JSP that will use SimpleTag. I'll write the JSP just like any other, with two additions: at the top I need to provide a Taglib Directive that makes the tag library available within the page. For my library this looks like:

<%@ taglib uri="jdj.tld" prefix="jdj" %>
The prefix lets me refer to individual tags within a library with the syntax <prefix:tagname>. The rest of my JSP looks like this:
<html>
<body>
Brought to you by: <jdj:simple/>
</body>
</html>

Now I need to deploy my new JSP file, jdj.tld, and SimpleTag.class to my Web server. For WebLogic, which I'm using, I've JARred all my classes and placed that JAR in the class path. I also need to copy the JSP file to /weblogic/myserver/public_html and the tld file to /weblogic/myserver/public_html/WEB_INF. Consult your JSP engine's documentation for details on deployment locations.

Once deployed, I simply navigate to the page with my browser (see Figure 1).

Figure 1
Figure 1

Enhancing SimpleTag
I've discussed the components of a custom JSP tag and the syntax for its use in a JSP. Now let's look at some more advanced tags to begin learning how to build really useful libraries.

SimpleTag didn't use a body, but suppose I wanted to have a tag that could output some text at the beginning and end of an arbitrary piece of content. I can easily extend SimpleTag to do this by implementing the doEndTag() method. This method works just like the doStartTag in that it obtains a JspWriter and prints content to it, but (as its name implies) the JSP engine always calls it when it encounters the close tag for a custom tag. Like doStartTag(), the doEndTag() method must also return an int. The possible values it can return are:

  • SKIP_PAGE: Instructs the JSP engine to ignore anything on the page that follows this tag
  • EVAL_PAGE: Instructs the JSP engine to continue processing the rest of the page as normal
I'll modify the SimpleTag to produce some simple font-formatting tags that we can surround some text with. The doStartTag method will output font and formatting information and the doEndTag will close our font tag. Now any text that is embedded in the JSP between <jdj:simple> and </jdj:simple> will take on those attributes.

Note that I've changed doStartTag to return EVAL_BODY_INCLUDE so the engine will evaluate and return any text between the tags (see Listing 2). EVAL_PAGE is returned by doEndTag so the rest of the page after my close tag is interpreted as well. Since I'm changing the content type of my body – that is, my body will no longer be empty – I also need to change the tld file. I do this by changing the value of <bodycontent> in the tld from "empty" to "JSP". This tells the JSP engine that my tag may contain standard JSP that should be treated as such (see Listing 2).

For my last step I just need to modify the JSP to put some text between the tags (see Figure 2 and Listing 3).

Figure 2
Figure 2

To make tags truly useful, they often need to be flexible enough to offer settable attributes to the tag user that help modify their behavior. For instance, with SimpleTag I may want to allow the tag user to decide what color the font is (see Listing 4). To support an attribute, I need to modify both my class file and deployment descriptor. First I need to add a method to the tag class called setXXX (where "XXX" is the attribute name). In this example the method is called setFontcolor (the first letter of any attribute is always capitalized when calling a "set" property). This method will set a private field within the tag that I'll use within doStartTag to set the color of the font.

The next step is including the attribute in jdj.tld. To do so, I add an attribute to the end of SimpleTag's <tag> definition.

<attribute>
<name>fontcolor</name>
required>false</name>
/attribute>

The "name" of my attribute is the name that users of the tag will refer to it by. For example, indicating "required," as you might assume, determines whether this attribute must be provided by the tag's user. Now I simply add the attribute to my tag within the JSP.

<jdj:simple fontcolor="green">It ain't easy being...</jdj:simple>
An attribute can even be the result of a runtime Java evaluation. To support this ability, I add the following line within the attribute definition:
<rtexprvalue>true</rtexprvalue>
A value of true here indicates to the JSP engine that it should evaluate any JSP appearing in the attribute and pass its result to the tag. So I could pass a value of red to my tag this way:
<jdj:simple name=<%= new String("red").toUppercase() %>/>
Tags That Modify Their Body
So far, my tag has always just ignored its body or included it verbatim. However, there are times when a tag might want to inspect and/or modify the contents of its body. For a tag to have this ability it must implement a different interface than Tag: BodyTag. Like TagSupport, BodyTag also has a convenience class that defines default methods for the interface called BodyTagSupport. A tag that implements BodyTag can parse the contents of its tag body and output anything based on that content. One example might be a tag that does simple conversion from HDML to WML or formats a block of text into paragraphs. For my example I'll create a simple body-modifying tag that will change any content in its body to uppercase. To do so I'll introduce a new method here that's useful only on BodyTag classes, called doAfterBody. This method gets called after doStartTag and before doEndTag, giving the tag author a place to inspect the tag body and either print it verbatim, ignore it or change it. My doAfterBody method looks like this:
public int doAfterBody() {
try {
BodyContent body = getBodyContent();
JspWriter writer = body.getEnclosingWriter();
writer.print(body.getString().toUppercase());
} catch (Exception x) {
return(SKIP_BODY);
}

For this tag I don't need a doStartTag or a doEndTag since I'm only concerned with changing the tag's body. You'll notice that the way to get the contents of the body is through a method called getBodyContent, which is defined in BodyTagSupport. From it I can get the actual String containing the contents of the body by calling getString(). Note also that while in doAfterBody, the JspWriter must be retrieved by calling getEnclosingWriter() from the BodyContent class instead of from the pageContext. The full listing for the class is in Listing 5.

The tld for the UppercaseTag looks like:

<tag>
<name>upper</name>
<tagclass>com.jdj.UpperCaseTag</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
Any content included between the <jdj:upper> and </jdj:upper> tags will now be forced to uppercase.

Like both doStartTag and doEndTag, doAfterBody must return an int value. UpperCaseTag returns a value of SKIP_BODY from doAfterBody. A value of SKIP_BODY causes the engine to continue processing the rest of the page and not include the body in its original form. Instead, the body is skipped and the contents that have been written to the JspWriter are included instead. The only other possible return value for doAfterBody is EVAL_BODY_TAG. A tag can return EVAL_BODY_TAG to cause the JSP engine to call doAfterBody another time. Use of EVAL_ BODY_TAG is common for tags that perform some looping – where a looping variable is checked at each call to doAfterBody() and SKIP_BODY is returned at the end of the loop.

Nested Tags
Another useful feature of JSP tags is the ability to have them work cooperatively by nesting them. When nested, outer tags can make methods and variables available to tags that are contained within their body. For example, I might want to define an outer tag that performs a JDBC query. I could then make the query's ResultSet available to inner tags that output individual column values. In this scenario the outer tag will have attributes that specify the driver to use, the JDBC connect String, and the SQL to execute. To make the results of the query available to inner tags, I need to define a public method that I'll call getDataValue(), which takes the column name and returns the String value for the current row and the specified column. The full definition of the QueryTag can be found in Listing 6. Note that with this tag I repeat the body contents by returning EVAL_BODY_TAG until I reach the end of the ResultSet, where I return SKIP_BODY.

Next I'll define an inner tag, which I'll call a DataValueTag. This tag will have a single attribute to specify which column to write the value of. Since DataValueTag will be nested within a QueryTag, I need to get the instance of the parent QueryTag and call getDataValue on it. Inner tags get access to their enclosing tags by calling findAncestorWithClass and passing it the inner tag's current instance (this) and the class type of the outer tag:

QueryTag qt = (QueryTag) findAncestorWithClass(this, QueryTag.class);
If qt is null, I throw an exception since my DataValueTag must appear within a QueryTag. If it isn't null, I do the following:
String val = qt.getDataValue( columnName );
out.print(val);
The code for the DataValueTag and tld for these tags can be found in Listings 7 and 8, respectively. By combining these two tags with, for example, an HTML table, I can easily create a JSP page that displays only the desired columns within each row in a particular SQL query.

JSP Life Cycle
Once you begin building custom tags and the JSP pages they're contained in, you may find yourself confused (as I was) about what's actually getting executed when. Without a clear picture of the life cycle and execution order of a page, your JSPs can give you unexpected results. Luckily, there's a simple flow of events and a couple of ground rules that can help clear this all up.

The first (and rather intuitive) ground rule is that page elements, whether lines of Java code or JSP Tags, are evaluated from top to bottom in the order in which they appear. When JSP code is encountered, it's simply executed; when a tag is encountered, its appropriate methods are called one at a time depending on the tag type. The following is the flow of methods that are called on a tag when it's encountered in a page:

  1. Two methods, setParent() and setPageContext(), are called on the TagHandler class. These methods are handled automatically by BodyTagSupport and TagSupport so you don't need to implement them explicitly if your tag extends either one of them.
  2. Any set methods for attributes on this tag are called.
  3. doStartTag() is called. If you haven't implemented this method, the flow continues. Otherwise you must return one of the following:
    • SKIP_BODY: Instructs the engine to ignore the body for this tag if one exists
    • EVAL_BODY_TAG: Instructs the engine to evaluate the body and call the Tag's doInitBody() method (relevant only for tags that implement the BodyTag interface; tags that extend from BodyTagSupport implement this interface)
    • EVAL_BODY_INCLUDE: Instructs the engine to evaluate and include anything in the tag body; engine proceeds to step 7 (relevant only for tags that implement the BodyTag interface; tags that extend from BodyTagSupport implement this interface)
  4. setBodyContent() is called on the tag. This allows classes that extend BodyTagSupport to evaluate, manipulate and modify the body of the tag.
  5. doInitBody is called. Any initialization necessary before doAfterBody is called can be done here (setting up Connections, setting variables in the pageContext, etc.).
  6. doAfterBody is called. A return value of SKIP_BODY here will result in doEndTag() being called. Returning EVAL_BODY_TAG will produce another call to doAfterBody.
  7. doEndTag() is called. A return value of EVAL_PAGE here will result in the rest of the JSP page being evaluated by the engine. Returning SKIP_PAGE will tell the engine to ignore the rest of the page.
It's sometimes useful to look at the source code for the servlet your JSP engine produces from a particular JSP to help understand when things occur in a page.

Sharing Variables
One last topic I'll cover is variable scope. What if I want to share data back and forth between tags and the JSP within a page? Can I pass variables back and forth? Well, I've already shown that tag attributes can be used to pass variables to a tag. But what about the inverse, making variables available to the JSP scope from within a tag? Can this be done? The answer, of course, is Yes, but the approach isn't quite intuitive. At first you might assume that you could just write a tag whose output is a line of Java code that defines a variable. Something like:

public class DefinesAVarTag extends BodyTagSupport {

public int doStartTag() {
try {
JspWriter out = pageContext.getOut();
out.println("<% String magazineTitle = \"JDJ\";%>");
} catch ( Exception e ) {
System.out.println( e );
}
}
}

Then use the tag like this:

<jdj:definesAVar/> <% if (magazineTitle.equalsIgnoreCase("JDJ") ) { %>
I love Java Developers Journal
But try to load this page and you'll see that there's a problem. The JSP returned by DefinesAVarTag isn't executed – it's actually just returned to the client as text. Meanwhile, the "if" statement is executed as standard JSP, which makes the compiler complain that the variable "magazineTitle" isn't defined.

This behavior exemplifies one of the ground rules for using custom tags: a JSP tag can't return Java code itself. Any output from a custom tag is sent as is to the client browser instead of being interpreted as Java. Though this might seem somewhat restrictive, the limitation actually makes sense from a performance standpoint. It allows a JSP page to be compiled into a Servlet class once, when first requested. Subsequent requests just run through the now-compiled Servlet class. If tags were permitted to return JSP themselves, the compiler would have to compile the page for every single request since the Java code output from a tag could differ from call to call.

Despite this limitation I can create a tag that defines and exposes variables as long as I decide the types and number of those variables up front. Once I've settled on what types (the actual Java classes) and how many variables a particular tag will put into scope, I incorporate that information into a class that extends the TagExtraInfo class. This class has only one method, which JSP engine calls to learn about the variables a particular tag defines. The method, called getVariableInfo, returns an array of VariableInfo objects that define the name, type and scope of our variables.

Here's an example:

public class JDJExtraInfo extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData data) {
return new VariableInfo[]
{
new VariableInfo("username",
"String",
true,
VariableInfo.NESTED)
};
}}
In this example the JDJExtraInfo class indicates that any tag associated with it will define one variable of type String called "username". The third parameter in the VariableInfo constructor will always be true for Java, while the fourth indicates what scope the variable should have. The possible scopes are:
  • VariableInfo.NESTED: Means the variable will be in scope for any JSP found between the open and close tags
  • VariableInfo.AT_BEGIN: Means that the variable will be in scope after the start tag and for the remainder of the page
  • VariableInfo.AT_END: Means that the variable won't be in scope until after the end tag and will remain in scope through the end of the page
Once I've created a TagExtraInfo class, I need to associate the tag with it. This is done with a new line in my tld file:
<tag> <name>membership</name> <tagclass>com.jdj.MemberShipTag</tagclass> <teiclass>com.jdj.JDJExtraInfo</teiclass> </tag>
Next I'll modify the tag to actually publish this variable. I do it by simply setting the variable in the pageContext from within any of the methods in my TagHandler. For example, the doStartTag might include the following line:
pageContext.setAttribute( "username", theUser );

where theUser is some String variable retrieved from elsewhere, like the session or the EJB layer.

Any variable exposed this way must match exactly the name and Java type of one of the VariableInfo objects in my TagExtraInfo class. Now I can use my membership tag within a JSP and refer to username like any other variable:

<jdj:membership>
Welcome back <%= username%>
</jdj:membership>
Conclusion
Custom JSP tag libraries are a valuable addition to the J2EE standard and offer several advantages over previous methods of separating logic and presentation within Java. By using the tactics introduced in this article, developers can begin building rich tag libraries that can abstract complex business logic from user interface design.

AUTHOR BIO
Adam Chace is a senior Web software engineer at Network World, Inc. (an IDG company), where he contributes to site architecture and development for Fusion (www.nwfusion.com) a leading site for networking professionals. He has over two years of experience developing wired and wireless Internet applications using server-side Java. He can be contacted at: [email protected]

	


Listing 1

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

public class SimpleTag extends TagSupport {
  
public int doStartTag() {
  try {
    JspWriter out = pageContext.getOut();
    out.println("Java Developers Journal");
  } catch (IOException ioe) {
    System.out.println("Error writing to out: " + ioe);
  }
    return(SKIP_BODY);
  }
}



Listing 2

public class SimpleTag extends TagSupport {
  
  public int doStartTag() {
    try {
      JspWriter out = pageContext.getOut();
      out.println("<font face=geneva size=8  color=blue>");
    } catch (IOException ioe) {
      System.out.println("Error writing to out: " + ioe);
    }
      return(EVAL_BODY_INCLUDE);
    }

  public int doEndTag() {
    try {
      JspWriter out = pageContext.getOut();
      out.println("</font>");
    } catch (IOException ioe) {
      System.out.println("Error writing to out: " + ioe);
    }
    return(EVAL_PAGE);
  }
}

Listing 3

<%@ taglib uri="jdj.tld" prefix="jdj" %>

<html>
  <body>
    <jdj:simple>
     Custom JSP Tags!
    </jdj:simple>
  </body>
</html>

Listing 4

private String color;

//Set via the tag attribute fontcolor
public void setFontcolor(String value ) {
  color = value;
}

public int doStartTag() {
  try {
    JspWriter out = pageContext.getOut();
    out.println("<font face=geneva size=8 color="+color+">");
 } catch (IOException ioe) {
    System.out.println("Error writing to out: " + ioe);
  }
    return(EVAL_BODY_INCLUDE);
  }

Listing 5

package com.jdj;

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

public class UpperCaseTag extends BodyTagSupport {

public int doAfterBody() {
  try {
    BodyContent body = getBodyContent();
    JspWriter out = body.getEnclosingWriter();
    out.print( body.getString().toUpperCase() );
  } catch (IOException ioe) {
    System.out.println("Error writing to out: " + ioe);
  }
    return(SKIP_BODY);
  }

}

Listing 6

package com.jdj;

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

public class QueryTag extends BodyTagSupport {

  private ResultSet rs = null;
  private Statement st = null;
  private Connection con = null;

  //Attributes
  private String connectString;
  private String driverClass;
  private String sql;
  private String user;
  private String password;


  public int doStartTag() {
    loadData();
    return( EVAL_BODY_TAG );
  }

  public int doEndTag() {
    closeConnections();
    return( EVAL_PAGE );
  }

  private void closeConnections() {
    try { rs.close(); } catch ( Exception e ) {};
    try { st.close(); } catch ( Exception e ) {};
    try { con.close(); } catch ( Exception e ) {};
  }

  private void loadData() {
    try {
      Class.forName( driverClass );
      con = DriverManager.getConnection( connectString, user, password );
      st = con.createStatement();
      rs = st.executeQuery( sql );
      rs.next();
    } catch ( Exception e ) {
      System.out.println("Error loading data: " + e );
    }
  }

  public int doAfterBody() {
    try {
      BodyContent body = getBodyContent();
      JspWriter out = body.getEnclosingWriter();
      out.println( body.getString() );
      //Clear the body (in case we loop again)
      body.clearBody();
      if ( rs.next()  ) {
         //There is another row so evaluate the body again
        return( EVAL_BODY_TAG );
      } else {
        //Last row so don't evaluate the body anymore
        return( SKIP_BODY );
      }
    } catch ( Exception e ) {
      System.out.println( "Error in doAfterBody: " + e );
      return( SKIP_BODY );
    }
  }

  //This is the method our nested tags will call to get the
  //value of a particular column in the current row
  public String getDataValue( String columnName ) throws SQLException {
    return( rs.getString( columnName ) );
  }

  public void setConnectString( String value ) {
    connectString = value;
  }

  public void setDriver( String value ) {
    driverClass = value;
  }

  public void setQuery( String value ) {
    sql = value;
  }

  public void setUser( String value ) {
    user = value;
  }

  public void setPass( String value ) {
    password = value;
  }

}

Listing 7

package com.jdj;

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

public class DataValueTag extends TagSupport {

  //Attributes
  private String columnName = "";

  public DataValueTag() {
  }
  
  public int doStartTag() {
    try {
      JspWriter out = pageContext.getOut();
      //Now try to get the parent
      QueryTag qt = (QueryTag)findAncestorWithClass(this, QueryTag.class);
      if ( qt == null ) {
       out.print( "Must be enclosed in a query tag!");
      } else {
          try {
            String val = qt.getDataValue( columnName );
            out.print( val );
        } catch ( Exception e ) {
          System.out.println(" An error occurred in DataValueTag " + e );
          out.println("An error occurred: " + e );
        }
      }
    } catch ( Exception ioe ) {
      System.out.println( "Error in doStarTag" + ioe );
    }
    return( SKIP_BODY );
  }


  public void setColname( String value ) {
    columnName = value;
  }
}

Listing 8

<tag>
  <name>query</name>
  <tagclass>com.jdj.QueryTag</tagclass>
  <bodycontent>JSP</bodycontent>
  <attribute>
    <name>sql</name>
    <required>true</required>
  </attribute>
  <attribute>
    <name>driver</name>
    <required>false</required>
  </attribute>
  <attribute>
    <name>connectString</name>
    <required>false</required>
  </attribute>
  <attribute>
    <name>user</name>
    <required>true</required>
  </attribute>
  <attribute>
    <name>pass</name>
    <required>true</required>
  </attribute>
</tag>

<tag>
  <name>data_value</name>
  <tagclass>com.jdj.DataValueTag</tagclass>
  <bodycontent>EMPTY</bodycontent>
  <attribute>
    <name>colname</name>
    <required>true</required>
  </attribute>
</tag>

  
 
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.