Skip to main content

Creating EL-Aware Taglibs Using XDoclet

June 18, 2004

{cs.r.title}








Contents
Defining a Case Study
First Refactoring: OO Reuse
Second Refactoring: Code Generation
XDoclet to the Rescue
Conclusion

When the JSP Tag Extensions (also known as taglibs) first came out, the only option to pass dynamic values as tag attributes was using Request Time (RT) expressions. With the advent of JSTL 1.0, another option has arisen: the Expression Language (EL).

This article assumes the reader already understand how EL works -- if you don't, a good introduction can be found in Sue Spielman's article, "Practical JSTL, Part 1."

The following code shows an example of RT and EL usage. First, we use RT to get the name of a user that is stored in the application context, given its login, passed as a parameter:

<%
String userName = "";
Map tmpMap = (Map) application.getAttribute( "usersMap" );
if ( tmpMap != null ) {
   User tmpUser = (User) tmpMap.get( request.getParameter("login") );
   if ( tmpUser != null && tmpUser.getName() != null ) {
      userName = tmpUser.getName();
   } // inner if
} // outer if
%>
Welcome <%= userName >!

Using EL and JSP 2.0, the same thing can be done much more concisely:

Welcome ${applicationScope.usersMap[param.login].name}!

As you can see in the example above, it is much easier for the page author to use EL rather than RT. But it is harder for taglib developers to implement custom tags that handle EL, as they have to explicitly write code in the tag handlers to evaluate the EL expressions at runtime.

The JSP 2.0 specification partially solves this problem, as it natively supports EL expressions -- an EL expression is evaluated by the JSP engine, which in turn passes the result to the tag handlers. I say partially because unfortunately, many applications still rely on a web container that only supports the JSP 1.2 (or even 1.1) specification.

In this article, we demonstrate how code generation tools (in our case, XDoclet) can be used to solve this problem, allowing taglib developers to focus on writing code that implements the tag features, and not code that evaluates its attributes.

Defining a Case Study

In order to explain our solution, let's first create a simple custom tag that will be refactored throughout the article. The tag will be called hello and its purpose will be to display a greeting message to the user, whose name is passed by the optional attribute user. If the user attribute is not set, a generic message will be displayed instead. The hello tag will be available in two taglibs: one that supports EL and another that only supports RT. Here is a JSP page with some examples of the tag's usage:

<%@ taglib uri="/WEB-INF/tld/v1/article-rt.tld" prefix="article-rt" %>
<%@ taglib uri="/WEB-INF/tld/v1/article-el.tld" prefix="article-el" %>

<h1>Request time examples</h1>
<article-rt:hello/><br>
<article-rt:hello user="felipe"/><br>
<article-rt:hello user="<%=request.getParameter("name")%>"/><br>
<br>
<h1>EL examples</h1>
<article-el:hello/><br>
<article-el:hello user="felipe"/><br>
<article-el:hello user="${param.name}"/><br>

The taglibs' TLDs are basically the same (they are available here and here); the only differences are the taglib URIs and the tag handler class names. But the tag handlers are different, as listed below:

public class HelloWorldRTTag extends TagSupport {
   private String user;
   public void setUser(String string) {
      this.user = string;
   }
   public int doStartTag() throws JspException {
      String greetings =
        (user == null || user.equals("")) ?
           "Hello world!" :
           "Hello " + user + "!";
      super.pageContext.getOut().write( greetings );
      return Tag.SKIP_BODY;
   }
}

import org.apache.taglibs.standard.lang.support.
  ExpressionEvaluatorManager;
public class HelloWorldELTag extends TagSupport {
   private String userEL;
   public void setUser(String string) {
      this.userEL = string;
   }
   public int doStartTag() throws JspException {
      String user = (String)
         ExpressionEvaluatorManager.evaluate(
            "user", this.userEL, String.class,
            this, super.pageContext );
      String greetings =
         (user == null || user.equals("")) ?
             "Hello world!" :
             "Hello " + user + "!";
      super.pageContext.getOut().write( greetings );
      return Tag.SKIP_BODY;
   }
}

Note that HelloWorldELTag uses ExpressionEvaluatorManager to evaluate the EL expressions at runtime. That class is available in standard.jar from the Jakarta Standard Taglib, which is JSTL's Reference Implementation.

The tag handlers are also shown in the class diagram in Figure 1:

Figure 1
Figure 1. Class diagram before any refactoring

First Refactoring: OO Reuse

If you closely inspect the method HelloWorldELTag.doStartTag() above, you realize it performs two operations:

  1. Gets the user name, evaluating the EL expression passed as attribute.
  2. Checks if the attribute is empty and then prints message.

You may also notice that HelloWorldRTTag.doStartTag() does almost the same, except that it does not need to evaluate the EL expression. Going one step further, we could say that both methods execute two operations:

  1. Resolve the attributes.
  2. Do the real job.

So, using plain OO techniques, we could create an abstract class that does the "real job" and defines an abstract method for resolving the attributes. Then we would extend that class by classes that supports RT or EL, as diagrammed in Figure 2:

Figure 2
Figure 2. Class diagram of the pure OO refactoring approach

And here is the relevant code:

public abstract class HelloWorldTagSupport
      extends TagSupport {
   protected String user;
   public final int doStartTag()
         throws JspException {
      resolveAttributes();  
      return doTheJob();
   }
   private int doTheJob() {
      String greetings = (user == null ||
         user.equals("")) ? "Hello world!" :
         "Hello " + user + "!";
      super.pageContext.getOut().write(greetings);
      return Tag.SKIP_BODY;
   }
   protected abstract void resolveAttributes()
      throws JspException;
}

public class HelloWorldRTTag
      extends HelloWorldTagSupport {
   public void setUser(String string) {
      super.user = string;
   }
   protected void resolveAttributes() {
      // do nothing
   }
}

public class HelloWorldELTag
      extends HelloWorldTagSupport {
   private String userEL;
   public void setUser(String string) {
      this.userEL = string;
   }
   protected void resolveAttributes()
      throws JspException {
        super.user = (String)
           ExpressionEvaluatorManager.evaluate(
              "user", this.userEL, String.class,
              this, super.pageContext );
   }
}

Note that in HelloWorldELTag, the method setUser() does not set the user, but rather the EL representing the user -- the user field itself will be set only on resolveAttributes.

Second Refactoring: Code Generation

Our first refactoring offered an elegant solution to the original problem, but it created another one: it increased the number of artifacts to be created for each tag handler. With this new architecture, the poor taglib developer now has to create three classes for each tag handler, plus two TLDs for the overall taglib! That overhead sounds not only counterproductive, but also error-prone.

What could we do next to improve this situation? Well, if you take a look on the current EJB specification (2.1), you realize this overhead is similar to that faced by EJB developers. Consequently, we should try the same solution adopted by many of them: using a code-generation tool that does the repetitive work, letting the developer focus on the real job.

Figure 3 shows the architecture for this new solution, with code-generated classes displayed in green:

Figure 3
Figure 3. Class diagram of the code-generation refactoring approach

Note that in this architecture we introduced one more class, HelloWorldTag, which now is the class responsible for doing the real job (previously, that was done by HelloWorldTagSupport). With this separation of responsibilities, we can now automatically generate HelloWorldTagSupport, HelloWorldRTTag, and HelloWorldELTag, as well. Now, all the happy taglib developer has to do is create HelloWorldTag, declaring its attributes and implementing its core logic (such as the methods doStartTag() or even doEndTag()) and the code generation tool will do the rest. In fact, HelloWorldTag behaves like any other tag handler, except for the fact that it does not need setters for its attributes (they will be automatically defined on its sub-classes).

Here is the code that has been changed (HelloWorldRTTag and HelloWorldELTag remained the same):

public abstract class HelloWorldTag
      extends TagSupport {
   protected String user;
   public int doStartTag() throws JspException {
      String greetings = (user == null ||
         user.equals("")) ? "Hello world!" :
         "Hello " + user + "!";
      write( greetings );
      return Tag.SKIP_BODY;
   }  
}

// Class automatically generated -
// please do not modify
public abstract class HelloWorldTagSupport
      extends HelloWorldTag {
   public int doStartTag() throws JspException {
      resolveAttributes();
      return super.doStartTag();
   }
   protected abstract void resolveAttributes()
      throws JspException;
   public abstract void setUser( String user );
}

XDoclet to the Rescue

Now that we've presented the theoretical stuff, let's jump to the practical side of the solution. Although we could use any code generation tool to implement our solution, we will stick to XDoclet, which is a well-known tool nowadays.

XDoclet works in a simple but straightforward way: it scans the source code for special Javadoc tags and uses templates to generate proper artifacts according to these tags. For instance, it generates all of the EJB interfaces and descriptors based on the XDoclet tags found in a simple file, the EJB implementation.

Currently, XDoclet already supports taglib development: it generates a TLD according to XDoclet tags found on the tag handler. The XDoclet tags are:

  • @jsp:tag: Defines a tag handler (must be used before the class declaration).
  • @jsp:attribute: Defines a tag attribute (must be used before each setter).

As an example, let's add XDoclet tags to our original tag handler:

/**
* @jsp:tag name="hello" body-content="empty"
*/
public class HelloWorldRTTag extends TagSupport {
   private String user;
   /**
    * @jsp:attribute name="user" required="false"
           rtexprvalue="true"
    */
   public void setUser(String string) {
      this.user = string;
   }
   public int doStartTag() throws JspException {
      String greetings = (user == null ||
         user.equals("")) ? "Hello world!" :
         "Hello " + user + "!";
      super.pageContext.getOut().write(greetings);
      return Tag.SKIP_BODY;
   }
}

What we need to do now is customize XDoclet to generate the three classes each tag handler requires (besides the TLDs, which it already generates). Note that the XDoclet tags will be used before the attributes declaration of our tag handler, not before the setters.

Our first step is to create one XDoclet template for each of these classes, and an Ant task that scans our source code and applies the templates to each tag handler found. I will call the new Ant task eltagdoclet and it can defined in a buildfile as shown below:

<eltagdoclet excludedTags="@version,@author,@todo"
      destDir="generated" verbose="true">
   <fileset dir="src" includes="**/*Tag.java" />
   <template templateFile="templates/TagSupport.j"
      destinationfile="{0}Support.java"/>
   <template templateFile="templates/ELTag.j"
      destinationfile="{0}EL.java"/>
   <template templateFile="templates/RTTag.j"
      destinationfile="{0}RT.java"/>
</eltagdoclet>

Now we need to create the templates. Let's take a look at TagSupport.j first:

/**
* Generated File - Do Not Edit!
*/
package <XDtPackage:packageOf><XDtClass:fullClassName/>
        </XDtPackage:packageOf>;
import javax.servlet.jsp.JspException;
public abstract class <XDtClass:className/>Support
      extends <XDtClass:className/> {
   public int doStartTag() throws JspException {  
      resolveAttributes();
      return super.doStartTag();
   }
   protected abstract void resolveAttributes()
      throws JspException;
}

This template is simple, as it only uses the name of the class being scanned. The other templates (RTTag.j and ELTag.j) are more complex, as they need to iterate through the attributes of the original tag handler in order to generate resolveAttributes() and the setters in the new class. For instance, the fragment below was extracted from ELTag.j:

   protected void resolveAttributes()
      throws JspException {
<XDtField:forAllFields>
  <XDtField:ifHasFieldTag tagName="jsp:attribute">
      super.<XDtField:fieldName/> =
         this.<XDtField:fieldName/>EL == null ? null :
         (<XDtField:fieldType/>)
         ExpressionEvaluatorManager.evaluate(
            "<XDtField:fieldName/>",
            this.<XDtField:fieldName/>EL,
            <XDtField:fieldType/>.class,
            this, super.pageContext );
  </XDtField:ifHasFieldTag>
</XDtField:forAllFields>
   }

Note: When I started writing this article, XDoclet's current version (1.2.1) didn't offer a way to capitalize an attribute name, so I created a patch and attached it to an existing XDoclet enhancement request. The patch was accepted, but as of this writing a new version has not been released yet. So, in order to use the solution proposed here, you have to either compile XDoclet from CVS or use the .jar files provided in this article's source.

Now that XDoclet has generated our classes using the new templates, we need to generate the TLDs for each taglib (the regular taglib and the taglib that supports EL). We could create new templates for the job, but as I said earlier, XDoclet already supports TLD generation (through the webdoclet Ant task), so it's easier to just take advantage of this feature. The trick now is to include XDoclet tags in the templates and then run webdoclet on the generated classes, as show in the buildfile fragment below:

<webdoclet excludedTags="@version,@author,@todo"
      verbose="true" destDir="web">
   <fileset dir="generated"
      includes="**/*TagRT.java" />
   <jsptaglib Jspversion="1.2" taglibversion="1.0"
      shortname="article"
      destinationFile="article.tld"
      destDir="web/WEB-INF/tld"/>
</webdoclet>
<webdoclet excludedTags="@version,@author,@todo"
      verbose="true" destDir="web">
   <fileset dir="generated"
      includes="**/*TagEL.java" />
   <jsptaglib Jspversion="1.2"  taglibversion="1.0"
      shortname="article-el"
      destinationFile="article-el.tld"
      destDir="web/WEB-INF/tld"/>
</webdoclet>

Finally, in order to keep this article short, I have shown only code snippets. The complete source code is available for download as source.zip.

Conclusion

This article demonstrates a simple, yet powerful solution for the problem of supporting EL in your custom taglibs. Although the source code included with this article is sufficient for most needs, there is still room for improvement, such as:

  • Evaluating EL inside of a tag body.
  • Generating TLDs and .jars specific for each JSP version.
  • Implementing eltagdoclet as a Java class.

In particular, one project that could take advantage of this approach is Jakarta Taglibs. Although this project hosts the JSTL Reference Implementation (as the Jakarta Standard Taglib), virtually all of the other taglibs developed there lack EL support. The reason for that deficiency is that the project is short of developers nowadays, so the few active committers (including yours truly) do not have spare time to manually add EL support to each tag handler (after all, there are dozens of tags distributed among all of the taglibs of the projects). In other words, we would like to eat our own dog food, but we need to do it in an efficient way. Hopefully, this article is a first step towards that direction. Once we make any progress, I will post a comment here or in my weblog.

Felipe Leme has worked professionally with Java since 1996, and in the last years had became an active enthusiastic of the technology.
Related Topics >> JSP   |   Web Development Tools   |   Web Services and XML   |