ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Accelerating JSP Tag Development with Jakarta Velocity

by Gregory Gerard
05/01/2002

Writing JSP tags can be tedious, even under the best of circumstances. Often, I merely wish to push out lightly-parameterized HTML to, for instance, control the layout of an intricate (but often used) table format.

While you might assert that this is precisely the purpose of Java Server Pages, with its directive for including JSPs through the <jsp:include .../> or <%@include ...%> elements, it can often be overkill and cumbersome to maintain. With a simple framework, you can quickly churn out JSP tags that provide compile-time type checking and near-normal Java calling semantics.

Here is a simple, if contrived, example comparing the JSP include method with the custom tag method. Because parameters of the tag are strongly typed, if you try to pass anything but a Collection to the tag, the JSP compiler will flag the error. In addition, required parameters that are missing are flagged at page compilation time, rather than at page evaluation time; earlier is better. Finally, <jsp:include/> can only pass a string directly to the included page. So if you wish to pass an object of your own creation, you must put it somewhere in the request or session and pass the name of it to the included page, a level of indirection that's simultaneously clumsy and ugly. The <%@include ...%> directive cannot pass anything on, so it's back to well-known names.



Listing 1. Iteration by Inclusion: The Includer

...
<%  java.util.List l = new java.util.LinkedList();
    l.add("first");
    l.add("second");
    l.add("third");
    l.add("fourth");
    request.setAttribute("myCollection", l);
%>

<jsp:include page="/IncludedPage.jsp" flush="true">
    <jsp:param name="collection" value="myCollection"/>
</jsp:include>
...

Here is the IncludedPage.jsp file, referenced above.

<%  String theCollectionName = request.getParameter("collection");
    java.util.Collection c = (java.util.Collection)
            request.getAttribute(theCollectionName);
    if (c != null)
    {
%>

collection name: <%=theCollectionName%>
collection value: <%=c.toString()%>

<%
    }
    else
    {
        // they forgot to set the parameter... *sigh*
    }
%>



Listing 2. Iteration by Inclusion: The Included Page

...
<ora:SomeCustomIterator collection="<%=mylist%>"/>
...

Tags are cool. Tags are, however, a pain to write for the same reasons Servlets are a pain to write; namely, putting HTML inside of Java code, which is neither pretty nor maintainable even in the short term. JSP was supposed to make mixing Java and markup easy.

To make things better and give an added dimension of power, we can add Jakarta Velocity to the mix. Velocity is a templating language, the philosophy of which is, "Keep the simple things simple." It sports a very concise syntax for inserting variable references into pretty much any type of textual material, from plain text to HTML to XML. It's not too picky or fussy. Moreover, it has some very convenient constructs for iterating over just about everything, and it can talk to everything from JavaBeans to custom objects to the lowly workhorse java.util.Map.

Download example files here.

Why not use Jakarta Velocity as is? Because it's not JSP. So if you have a mandate to use JSP, Velocity by itself isn't a solution. Why not use the Velocity Tag Library? While this approach gives direct access to Velocity's macros, it doesn't solve many of our problems. First, it's no better than JSP in exposing business logic to an HTML author. Second, it doesn't provide any sort of interpage reusability like JSP taglibs do, other than <jsp:include .../>. Third, it doesn't provide any sort of type checking for the HTML author.

Instead, we factor out the HTML into a separate resource that is palatable to many more HTML editors and tools. Obviously, these tools cannot expand any runtime macros, but neither will they hide behind JSP-isms so much as to be useless. The solution is a very simple boilerplate you can use to crank out useful tags as quickly as you need them. We'll make four files in total: one Java class that is our tag code, one XML file that is our tag descriptor, one template HTML file, and one JSP file that will serve as a demo page.

Java Server Pages

Related Reading

Java Server Pages
By Hans Bergsten

One extra benefit from this setup is that the taglib author can test the markup generation largely outside of the application server. Those who know the woes of appserver redeployments will appreciate this.

The Java class is relatively short. First, it validates its tag parameters. This is where you embed your custom business logic. Once satisfied with the validity of tag parameters, we put them all into the Velocity Context object to make them accessible to the markup template. Finally, we let the tag engine know to "paint" the tag to the JSP output stream.

VelocityContext myVelocityContext = getVelocityContext();
myVelocityContext.put("title", title);
myVelocityContext.put("subtitle", subtitle);
myVelocityContext.put("images", images);
myVelocityContext.put("ingredients", ingredients);

StringWriter myStringWriter = new StringWriter();

Velocity.mergeTemplate(myResource,
org.apache.velocity.runtime.RuntimeSingleton.getString(Velocity.INPUT_ENCODING,
Velocity.ENCODING_DEFAULT),
myVelocityContext, myStringWriter);
pageContext.getOut().print(myStringWriter);

return EVAL_PAGE;

The taglib control file is an XML file that lets the JSP engine know what tags we are publishing. It declares the tag name, what Java class is associated with it, and whether a parameter is required or not.

The markup file is anything you like. It's put in the class path and loaded accordingly. You can use Velocity macros in this file as you'd like. You have full access to all objects you placed into the Velocity Context object. You can create more advanced macros within this file as well.

<table border="0" cellspacing="2" cellpadding="2" title="$title">
#foreach ( $key in $ingredients.keySet() )
<tr>
    <td>$key</td>
    <td>$ingredients.get($key)</td>
</tr>
#end
</table>

## This handy macro will only print out an attribute if there is a value
## provided.  This prevents HTML from having things like
## class="" and instead suppresses the entire attribute.
## <div #condAttribute("class" $class)> ... </div>
#macro (condAttribute $name $value)
#if ( $value )
#if ( $name )
${name}="${value}"#end#end#end

#condAttribute("summary" $subtitle)

Finally, we have our test JSP to host our sample tag.

<%@ taglib uri="eztag-sample" prefix="ora"%>
<%  java.util.Map myIngredients = new java.util.HashMap();
    myIngredients.put("polenta", "1 cup");
    myIngredients.put("milk", "2 cups");
    myIngredients.put("water", "2 cups");
    myIngredients.put("moscarpone cheese", "1/2 cup");
%>
<ora:sample title="Baked Polenta" ingredients="<%=myIngredients%>"/>

Give it whirl! You'll find managing your content generation and template to be far more convenient.

References

Gregory Gerard is an engineer, though not the cool kind that drives a train. He is a managing partner of Southgate Consulting Group.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.