ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Migrating to Velocity
Pages: 1, 2

Duplicating a JSP

Taking a look at a basic JSP, there are still some features that are required in order to facilitate our migration.



In the example below, we set the content type, instantiate a JavaBean (a Date object), include a couple of JSP "fragment" files, and use the JSTL taglib fmt in order to display a heading and message from a ResourceBundle. The last line calls the getTime method on the Date (this could just as easily be a call to the JSTL c:out tag to get the same data).

<%@ page contentType="text/html; charset=UTF-8" %>
<fmt:setBundle basename="com.lateralnz.messages" var="sysm" />
<jsp:useBean id="test" scope="session" class="java.util.Date" />
<html xmlns="http://www.w3.org/1999/xhtml">

<%@ include file="htmlhead.jsf" %>

<body>
<%@ include file="header.jsf" %>

<h1><fmt:message key="app.welcome_title" bundle="${sysm}" /></h1>

<p><fmt:message key="app.welcome_text" bundle="${sysm}" /></p>

<p><%= test.getTime() %></p>

</div>
</body>
</html>

Velocity provides directives to duplicate some, but not all, of this JSP. In a typical Velocity installation, a servlet would place the Date and ResourceBundle objects into the context so they could be called from within the template. Step two then, is to get around this necessity (ignoring for a moment that doing so goes back somewhat on that MVC goal I mentioned earlier). I create my own directive called #usebean (see UseBeanDirective.java for the code), which is passed a name, the name of the class to use, and its scope. UseBeanDirective then creates the JavaBean and puts it in the context. If the scope is "application", it stores the bean in a static HashMap for later use; if the scope is "session" it is stored in the user's session.

Also required is a method in VMServlet to work out the content type of the template, in a similar manner to how it is set in a JSP. My current choice is to cache the content types, keyed by the requestURI, and use the getData method of the Velocity template to look for a variable ($contentType) in the page:

private final String determineContentType(
    Node node) {
  for (int i = 0; 
       i < node.jjtGetNumChildren(); 
       i++) {
    StringBuffer sb = new StringBuffer();
    Node n = node.jjtGetChild(i);
    
    if (n instanceof org.apache.velocity.
          runtime.parser.node.ASTSetDirective) {
      Token t = 
        findTokenWithPrefix(n.getFirstToken(), 
                            "$");
      if (t == null || 
         !t.toString().equalsIgnoreCase(
             "$contentType")) {
        continue;
      }
      else {
        t = findTokenWithPrefix(t, "\"");
        String ct = StringUtils.remove(
                      t.toString(), "\"");
        return ct;
      }
    }
  }

  return "text/html";
}

private final Token findTokenWithPrefix(
    Token t, 
    String prefix) {
    
  if (t == null) {
    return null;
  }
  else if (t.toString().startsWith(prefix)) {
    return t;
  }
  else {
    return findTokenWithPrefix(t.next, prefix);
  }
}

This, admittedly, is a hack, but it does the job. I, therefore, add the following to processRequest:

String contentType;
if (contentTypes.containsKey(requestURI)) {
  contentType = 
      (String)contentTypes.get(requestURI);
}
else {
  contentType = 
      determineContentType((Node)tmp.getData());
  contentTypes.put(requestURI, contentType);
}

The final Velocity template then, looks like this:

#set ( $contentType = "text/html" )
<html xmlns="http://www.w3.org/1999/xhtml">

#usebean ( test "java.util.Date" page )
#usebean ( sysm 
  "com.lateralnz.MyMessagesBundle" application )

#parse ( "htmlhead.vm" )

<body>
#include ( "header.vm" )

<h1>
$sysm.getString('app.welcome_title')
</h1>

<p>
$sysm.getString('app.welcome_text')
</p>

<p>$test.getTime()</p>

</body>
</html>

Not a huge difference from the original JSP, as you can see. I've shown the ResourceBundle loaded as a custom-written JavaBean, but in our actual system, I cache the bundle in VMServlet, and load it automatically as an implicit object.

There are a few missing bits and pieces I haven't mentioned. If you're using the JSTL, then you're probably also using some of the other Jakarta taglibs. If that's the case, you may have some additional coding to do. Luckily, it's fairly easy to extend Velocity by creating new directives (in fact, I've found it's easier to come to grips with them than it is to write taglibs, despite the relatively poor API documentation). So far, I've only needed to create a couple of extra directives: one for formatting dates, one for formatting numbers, and a simple directive to halt the rendering of a template (based upon a tip posted in the Velocity mailing lists):

public class ExitDirective extends Directive {
  private static final String EXIT = "exit";
  private static final 
    StopRenderingException STOP_RENDER_EXCEPTION = 
      new StopRenderingException();
  
  public boolean render(
      InternalContextAdapter context, 
      Writer writer, 
      Node node) 
        throws IOException, 
           ResourceNotFoundException, 
           ParseErrorException, 
           MethodInvocationException {
    throw STOP_RENDER_EXCEPTION;
  }
  
  public int getType() {
    return DirectiveConstants.LINE;
  }
  
  public String getName() {
    return EXIT;
  }
}

Within the processRequest method of VMServlet, the StopRenderingException is caught, and effectively halts the template rendering process. In our system, we use this particular directive in the header of a WAP page to drop out, in the case of a user not being logged into the system:

#if ( !$session.getAttribute(USER_ID) )
<card id="message" title="Invalid Session">
<p>Sorry, but your session has 
timed out.</p>
</card>
</wml>
#exit ( )
#end

Conclusion

Velocity provides the web developer with a powerful templating mechanism — however, this can present a significant barrier of entry to those who are interested in migrating away from JSPs and taglibs. Using a simple servlet, and extending Velocity, with its concept of directives, I hope to have shown that you can mimic a considerable proportion of the functionality you get from JSPs, and therefore take your migration one small, low risk, step at a time.

References

One final note: if you're developing on some version of *nix, don't forget the wonders of sed for automating some of the sheer drudgery in porting your JSPs. I'm not a sed expert, but it still only took a few minutes to come up with something that reduced the amount of manual code changes by about 90 percent.

Jason R. Briggs is an architect/developer working in the United Kingdom.


Return to ONJava.com.