Custom JSP Actions
Over the last few months, we have looked at server-side Java from a number of perspectives. We began with servlets, Java classes that are executed from within a servlet container. While programmers are not especially daunted by servlets, graphic designers might feel otherwise.
Solving this problem are JavaServer Pages (JSPs) that combine Java and HTML, using a syntax similar to Microsoft's Active Server Pages (ASP) or the open-source HTML::Mason system for mod_perl. Each JSP is really a servlet in disguise; the JSP engine translates the page into a servlet, and then compiles the servlet into a Java .class file.
JSPs can include straight Java code that can make it easier to perform complex actions. But at a certain point, this code can overwhelm the HTML, making it impossible to maintain the JSP. Nonprogrammers are also turned off by large amounts of code inside a JSP, defeating much of the purpose of using JSPs instead of straight servlets.
Last month, we looked at one way to avoid putting code inside of JSPs using JavaBeans. Using simple XML-based tags, a nonprogrammer can put together JSPs that exhibit complex behaviors, without having to write a single line of code. Indeed, the real magic of JavaBeans is not the beans themselves, but rather the special tags that allow us to work with them so easily.
This month, we will learn how to write our own “custom actions”, as they are known—XML-based tags that allow us to work with Java classes and methods without having to work with Java itself. Our examples are designed to work with the open-source Jakarta-Tomcat implementation of servlets and JSPs. However, they should work with any JSP implementation that works with custom actions.
There are a number of reasons to use custom tags. First of all, they reduce the amount of Java code we must put inside of our JSPs, making them easier to read, understand and maintain. In addition, custom tags are less complicated than Java code, making them suitable for a wider audience than Java code users. Finally, each custom tag library points to a centrally written and maintained Java class. Using custom actions, a site can thus create a library of tags appropriate for its particular needs. One Java programmer can create and publish a tag library for a number of graphic designers and JSP authors. As we will see, custom tags aren't a panacea, but they can be quite useful, and I consider them one of the most compelling reasons to use JSP over competing technologies.
Custom actions provide us with a shorthand for complex Java code within our JSP. Anything you do with a custom action could also be accomplished with Java code inside of the scriptlet (<% %>) tag. After all, the JSP is turned into a servlet before it is compiled and executed for end users.
As we saw last month with JavaBean tags, custom actions are defined with XML rather than HTML. This can be confusing and frustrating at first, especially for those of us who have acquired bad habits when writing HTML. The following might appear to be legal:
<P><jsp:getProperty name="simple" property="userID"></P>
But in fact, the above line will not work and will result in an exception and stack trace within the JSP. That's because all tags in XML must be closed somehow. If a <tag> has no matching </tag>, then it must indicate that it closes itself with <tag/>. Thus, the above line must actually be written as:
<P><jsp:getProperty name="simple" property="userID"/></P>Custom actions are merely syntactic sugar for Java methods. Each tag library defines a set of actions. For example, the jsp tag library defines three actions: “getProperty”, “setProperty” and “useBean”. Each action is defined by a single Java class, known as a tag handler.
To define a tag library, we create an XML file known as a tag library descriptor, or TLD. The TLD connects each action to its appropriate tag handler class, listing optional and mandatory attributes, as well as other information about the tags.
To use our custom actions within a JSP, we use a special directive to load our TLD. This helps the JSP engine to validate the custom tags within our JSP, as well as to find the tag handler class associated with these actions.
We will now define one simple custom action in order to understand the underlying mechanics of working with tag handler classes, TLDs and JSPs.
Our custom action will be a “hello” tag, which takes an optional “firstname” parameter. If the parameter is there, our tag will produce a simple “hello” message to the named user. If the parameter is missing, our tag will produce a generic “hello” message.
The first step is to write a simple tag handler that will implement this functionality. Such a tag handler is shown in Listing 1, defining the HelloTag class. I put the HelloTag.java source file, along with all JSP- and servlet-related classes, under the $TOMCAT_HOME/classes directory. Since HelloTag.java is in the il.co.lerner package, and since $TOMCAT_HOME on my machine is /usr/java/jakarta-tomcat-3.2.1, this means I placed my Java source file in:
After compiling HelloTag.java into HelloTag.class, this tag handler can be incorporated into one or more tag libraries.
Each tag handler class must implement one of two different standard interfaces, Tag or BodyTag. (The latter is for custom actions that have a body between their opening and closing tags, rather than those we will discuss this month, which have no body.)
In practice, there is no reason to implement these interfaces. It is easier and more practical to inherit from the TagSupport and BodyTagSupport classes, which provide default implementations for the interfaces. By subclassing TagSupport, we can save ourselves some work, overriding only those methods for which we don't want the default behavior. In the end, our implementation of HelloTag requires only three methods: setFirstname, doEndTag and release.
The first method, setFirstname, looks and acts just like a JavaBean property-setting method, taking a single argument and returning void. setFirstname is invoked automatically when the JSP engine encounters our custom action with a “firstname” parameter. The parameter value is set to the value passed in the tag. As with JavaBeans, the method that sets firstname must be named setFirstname, with a capital “F”.
Our second method, doEndTag, is invoked when the JSP engine encounters our custom action's closing tag. The doEndTag method takes no arguments and returns an integer. But instead of returning an integer, we will return one of the symbolic constants provided for us. Normally, we will return EVAL_PAGE, which tells the JSP engine that it should continue to evaluate the remainder of the JSP from which our custom action was invoked. If we wish to stop the JSP engine from evaluating the file any more, either because we have encountered an error or because we want to forward the user to another URL, we can return SKIP_PAGE instead.
Inside of doEndTag, we can place any Java code we might like. In addition to any instance variables we create, we have access to information about the JSP itself, including its HTTP request and response. This is how we can write information to the user's browser, replacing the custom tag with HTML, XML or plain text. (Custom actions generally return plain text, allowing the JSP author to choose how that text will be formatted.) Using the PageContext object, defined by our TagSupport superclass, we can retrieve an output stream and send data to it:
Finally, we define the release method, which takes no parameters and returns void. release() is invoked when the custom action has finished execution, and it gives the tag handler class a chance to clean up after itself. In general, this means setting each of the instance variables to null, but it might also involve closing a connection to a relational database or sending information to the error log. In HelloTag.java, we simply assign firstname the null value, and then ask our superclass to nullify each of its own values.
Now that we understand each of the individual methods in HelloTag, how do they work together? When a JSP contains a custom action mapped to our class (via the TLD, described below), each of the action's parameters invokes a “set” method in our class. For example, someone passing the parameter firstname=“foo” will effectively invoke setFirstname(“foo”).
Since we want to make firstname an optional parameter, we give it a default value (null) when we first create it. When the JSP engine finishes evaluating our custom action, it invokes doEndTag and looks at the value of firstname. If firstname is null, it sends a generic (“Hi there!”) message to the end user. If firstname is non-null, however, doEndTag uses its value to send a more personal message to the end user.
When the custom action has finished executing, the JSP engine invokes release(), resetting firstname and a number of other objects.