Creating EL-Aware Taglibs Using XDoclet Blog

    {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 userattribute 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 usesExpressionEvaluatorManager to evaluate the EL expressions at runtime. That class is available instandard.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 methodHelloWorldELTag.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 thatHelloWorldRTTag.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 methodsetUser() does not set the user, but rather the EL representing the user -- the user field itself will be set only onresolveAttributes.

    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 byHelloWorldTagSupport). With this separation of responsibilities, we can now automatically generateHelloWorldTagSupport, 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 evendoEndTag()) 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 HelloWorldELTagremained 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 atTagSupport.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 andELTag.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 fromELTag.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 runwebdoclet 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.

      
    http://today.java.net/im/a.gif