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.
Once we have written our class, we can write a TLD that describes it to the JSP engine. Many people might prefer to work in the opposite direction, using the TLD as a specification JSP authors and tag handlers can use while working in parallel. I prefer to write the custom actions first, modifying the TLD as I go along, even though this is admittedly not the safest nor the most elegant means of working.
The TLD, as you can see from Listing 2, can be a relatively short XML file. The TLD maps action names to the classes that implement those actions. A TLD can map a single action to a single class, or it might map hundreds of actions to hundreds of different classes. And because each class exists separately, it is even possible (though hardly a good idea) for a class to be used in multiple TLDs simultaneously.
The TLD is loaded into our servlet container when it is first referenced. Unfortunately, this means that changing the TLD after the custom action has already been invoked requires restarting Tomcat (and Apache, if you are using Apache's mod_jk along with the Tomcat server). It tells the JSP engine which versions and specifications your tag library supports, making it possible for a JSP engine to know when a particular library needs to be upgraded in order to be compatible with current standards.
The TLD consists of a top-level <taglib> tag, which contains a minimum of four sections: <tlibversion> indicates the version of the tag library specification this library supports; <jspversion> indicates the version of the JSP specification for which the tag library was written; <shortname> gives this tag library a name, which some JSP engines use; and <tag> appears once for every tag handler class we want to include in our library. Each tag gets its own name, the name of the action that is invoked. Thus, if we import a tag library with a prefix of “abc”, the tag named “hello” will be invoked as “abc:hello”. The <tagclass> section maps the tag's name to the tag handler class that actually performs the actions; this class must obviously be in your server's CLASSPATH. The <info> section allows us to provide some basic information and in-line documentation about this particular tag.
Finally, we name each of the attributes this custom action takes. Each attribute has its own <name> tag, as well as an indication of whether the attribute is required.
Now that we have a TLD and a tag handler class, we can use them together in any of our JSPs. We import the tag library using the special JSP taglib directive:
<%@ taglib uri="/WEB-INF/hello.tld" prefix="hello" %>
Notice how the taglib directive takes two parameters, “uri” and “prefix”. The uri portion contains the filename of the TLD that we just created. If you want to put TLDs directly inside your WEB-INF directory, then the above syntax is perfectly valid. The prefix parameter is a sort of namespace declaration, telling the JSP engine what prefix we will attach to each of the actions the tag library imports. Giving the JSP the option of naming the prefix, rather than building it into the tag library itself, allows us to import multiple tag libraries without having to worry about namespace clashes.
Since our TLD defines a single “hello” tag, and since we imported the tag library using the “hello” prefix, we can invoke our HelloTag methods using the following syntax: <hello:hello/>. Listing 3 contains a complete JSP (test-tag.jsp) that demonstrates how we can use this tag.
Remember to include the trailing slash when invoking custom actions. If you forget to include it, Tomcat's JSP engine (known as Jasper) will produce an error message similar to the following:
Unterminated user-defined tag: ending tag </hello:hello> not found or incorrectly nested
Our TLD indicates the firstname attribute is optional. If we don't pass a firstname parameter, then we get the following output in our web browser:
This is a test of our custom action. Hi there!We can also pass an optional firstname parameter:
<hello:hello firstname="Reuven"/>If we put the above in our JSP, the following output is sent to the browser:
This is a test of our custom action. Hello, Reuven
The above is a trivial example of how custom actions work. Custom action tags can do much more than simply print names. For example, objects can connect to a relational database, retrieving (or storing) information without requiring explicit Java inside of our JSPs. Custom actions can also act as iterators or provide us with conditional execution.
In order to perform these more advanced actions, we will take advantage of the fact that a tag handler class can look at the body of a custom action; that is, whatever text might happen to reside between the action's opening and closing tags. We can do all sorts of things with this text, ranging from iteration and conditional execution to asking the JSP engine to evaluate its contents before passing it to the tag handler. It is even possible to nest one tag inside of another, effectively passing values from one action to another.
There are a number of open-source tag libraries, including one provided by the Jakarta project itself, which use these functions to provide a great deal of functionality in a number of tags.
Custom actions are an extremely powerful tool. They provide a wealth of advantages over putting straight Java code inside of JSPs, encapsulate complex behavior inside of easy-to-remember tags, make it relatively easy for nonprogrammers to work with databases and other nontrivial systems.
But there is a problem with custom actions that can be traced back to the word “custom”. The ability to define your own tags within JSPs is a clever and sophisticated tool and provides a number of benefits to everyone involved in developing a web site. However, part of the beauty of the Web is that it is relatively standardized.
Moreover, custom actions can be used to create an entirely new language written in Java and implemented in tag handler classes. Hans Bergsten, whose book, JavaServer Pages, provides excellent information and instruction in JSPs, pushes this idea to the limit, effectively removing the need for Java within JSPs. However, it disturbs me to see the replacement of a relatively stable and well-known language (Java) with a new, less-known and less battle-tested language (his custom tag libraries).
If I were working at a large corporation that had decided to make a major investment in Java, servlets and JSPs, I would feel quite comfortable using custom actions. Such a company is in a position to create its own tag library that can be used over the life of a web site, defining its own standards for how things work.
But for those of us working outside of a large corporation, or who work with a number of different clients, interoperability is a paramount concern. If each of my clients were to define a different set of custom actions for their sites, I would find myself struggling to remember which tags and attributes I need to use for loops, database access and conditional execution. And as I indicated above, I worry about working with nonprogrammers who already struggle with the idea of learning to embed Java inside of their HTML pages—teaching them two different types of loops (one in Java, and another with custom actions) will undoubtedly lead to some confusion.
A good compromise solution might be the inclusion of a large, standard set of custom actions that will be made part of the JSP specification, much as has been done with JavaBean-related tags. The tag library presented in Bergsten's Java Server Pages is a good start but is only one of many such available libraries. It would be nice to see the JSP community get together on this issue, before we find ourselves faced with dozens of similar but incompatible libraries, some of which will undoubtedly be proprietary.
JSPs are a powerful and quick way to work with server-side Java, particularly for nonprogrammers who don't want to learn a language. Custom actions, particularly when combined with JavaBean components, make it possible to perform complex tasks with a minimum of code. With some forethought, a site can avoid inserting nearly any Java code into their JSPs, relying instead on custom actions and tag libraries.
However, sites (and consultants who use custom actions) should balance the convenience and power of tag libraries with the fact that they are effectively creating a new programming language. If we aren't careful, custom tags will cause a split in the server-side Java community, fracturing it into subcommunities that use different, incompatible libraries.