JaVa
   

Velocity

This section discusses how to install and configure Velocity 1.3, and the framework's built-in support for the Velocity template engine.

Installing and Configuring Velocity

Unlike JSP, Velocity isn't part of J2EE and probably won't be shipped with your app server. The /lib/runtime/velocity directory of the sample app includes the two JAR files that Velocity 1.3 requires: velocity-1.3.jar, and velocity-dep-1.3.jar. These must be copied to the /WEB-INF/lib of a web app using Velocity. The sample app's Ant build script handles this. Velocity must also be configured before individual templates can be used. Velocity is highly configurable and exposes many configuration parameters, but the only configuration parameters that we must set tell Velocity where to find templates at runtime. Velocity offers several strategies for loading templates, such as using the file system or the class loader. In the sample app, I've opted for the class loader, as it's more portable between web containers. The following keys from the sample app's /WEB-INF/velocity.properties file tell Velocity to load templates from the classpath (that is, under /WEB-INF/classes or /WEB-INF/lib within the WAR), and that it should automatically reload altered templates to ease development:

 resource.loader=class
 class.resource.loader.class =
 org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
 class.resource.loader.cache = false
 class.resource.loader.modificationCheckInterval = 5


Important 

Velocity template reloading, like JSP compilation, should normally be switched off in production, as it will impact on performance.

Important optional settings include the paths of Velocity macro libraries to be loaded when Velocity starts, and made available to all templates. The following example specifies the location of a Velocity macro library:

 velocimacro.library = myMacros.vm


Multiple libraries can be included in a CSV list, and they should be located as for ordinary Velocity templates. We need to apply these properties to Velocity before we attempt to evaluate templates. One way to do this is to use a servlet with a <load-on-startup> subelement to ensure it's loaded before our web app receives requests. However, the bean-based framework we discussed in provides a more elegant alternative. As a "singleton" bean in our app context is guaranteed to be instantiated, even if it's not accessed by user code, we can simply define a bean that will configure Velocity during app initialization. The framework provides the com.interface21.web.servlet.velocity.VelocityConfigurer bean for this purpose. The following bean definition from the /WEB-INF/ticket-servlet.xml file of our sample app illustrates its use:

 <bean 
 class="com.interface21.web.servlet.view.velocity.VelocityConfigurer">
 <property >/WEB-INF/velocity.properties</property>
 </bean>


Note the single bean property url, which is the location within the WAR of the standard Velocity properties file. The VelocityConfigurer bean loads the Velocity properties (in this case from a properties file within the WAR), and uses them to initialize the Velocity singleton as follows:

 Velocity.init(properties);


Note 

Velocity 1.2 introduced support for multiple independent instances of Velocity in the same web app, so the Singleton model is no longer the only choice.

Implementing the View Interface for Velocity

Let's look at the framework's built-in implementation of the com.interface21.web.servlet.View interface, which exposes model data via Velocity templates. This framework implementation needs to perform the following steps in its render() method:

Exposing Model Data to a Velocity Template

Please look at the source of the com.interface21.web.servlet.view.velocity.VelocityView class in the /framework directory of the download for the full implementation. I show the basic steps to achieve each of these steps below. Like the other view implementations discussed in this chapter, VelocityView extends AbstractView. The first step in the renderMergedOutputModel() method is to is to create a Velocity context to build this response:

 Context vContext = new VelocityContext() ;


Model objects are exposed to this new, request-specific, Velocity context in the following private method:

 private void exposeModelsAsContextAttributes (Map model, Context vContext) {
 if (model != null) {
 Set keys = model.keyset();
 Iterator itr = keys.iterator();
 while (itr.hasNext() ) {
 String modelname = (String) itr.next();
 Object val = model.get (modelname) ;
 vContext.put (modelname, val) ; 
 }
 }
 }



The template can be obtained from the Velocity singleton instance and cached when the view is initialized as follows:

 private void loadTemplate() throws ServletException {
 String mesg = "Velocity resource loader is: ["+
 Velocity.getProperty ("class. resource. loader. class") + "]; ";
 try {
 this.velocityTemplate =
 RuntimeSingleton. getTemplate (this. templateName); 
 }
 catch (ResourceNotFoundException ex) {
 mesg += "Can't load Velocity template "' + this. templateName +
 " ': is it on the classpath, under /WEB-INF/classes?" ;
 logger.logp(Level. SEVERE, getClass().getName() , "getTemplate" ,
 mesg, ex) ;
 throw new ServletException(mesg, ex);
 }
 catch (ParseErrorException ex) {
 mesg += "Error parsing Velocity template "' + this. templateName + " ' " ;
 logger.logp(Level.SEVERE, getClass().getName() , "getTemplate" ,
 mesg, ex) ;
 throw new ServletException(mesg, ex) ;
 }
 catch (Exception ex) {
 mesg += "Unexpected error getting Velocity template ' " +
 this. templateName + " ' " ;
 logger.logp (Level.SEVERE, getClass().getName(), "getTemplate",
 mesg, ex) ;
 throw new ServletException(mesg, ex) ;
 }
 }



With a Velocity context populated with model data, we can generate output to the HttpServletResponse as follows:

 this.velocityTemplate. merge (vContext, response. getWriter() ) ;


Note 

The actual implementation of the VelocityView class uses a more performant, but slightly more complex approach, based on the VelocityServlet bundled with Velocity. Please see the source for details.

Providing Support for Date and Currency Formatting

Surprisingly, Velocity 1.3 lacks standard support for date and currency formatting. The Velocity Tools project includes some support, although it still seems to be in development. Thus to ensure that our framework's Velocity view isn't tied to the current locale, we need to help Velocity out in this area. We could opt to expose formatted dates and currency amounts as string properties in each model, but this subverts good design. Localization is usually better handled in views than in models. The VelocityView class adds java.text.SimpleDateFormat and java.text.NumberFormat objects to the Velocity context if the view definition indicates that the wrapped template needs to perform date or currency formatting. Java's localizable support for number and currency formatting is simple, familiar is and documented in the java.text package Javadocs. SimpleDateFormat and NumberFormat objects must be constructed for each request, as Velocity template code may alter their configuration - for example, by setting the formatting pattern. These helpers are exposed only if two bean properties on VelocityView - exposeDateFormatter and exposeCurrencyFormatter - are set to true to indicate that a particular View instance is associated with a template that needs to format dates or currency amounts (exposing these helper objects to the majority of templates that don't need them would be wasteful). The helpers are initialized using the HttpServletRequest's locale property, as follows:

 public static final String DATE_FORMAT_KEY = "simpleDateFormat";
 public static final String CURRENCY_FORMAT_KEY = "currencyFormat";
 private void exposeHelpers(Context vContext, HttpServletRequest request) {
 if (this.exposeDateFormatter) {
 // Javadocs indicate that this cast will work in most locales
 SimpleDateFormat df = (SimpleDateFormat)
 DateFormat.getDateTimeInstance(DateFormat.LONG,
 DateFormat.LONG, request.getLocale());
 vContext. put (DATE_FORMAT_KEY, df) ; 
 }
 if (this.exposeCurrencyFormatter) {
 NumberFormat nf =
 NumberFormat.getCurrencyInstance(request.getLocale());
 vContext. put (CURRENCY_FORMAT_KEY, nf) ; 
 }
 }



We can now manipulate patterns if necessary in Velocity templates and use the objects to format model data as follows:

 The date of the performance is
 $simpleDateFormat.format($performance.when).
 The total cost of these tickets will be
 $currencyFormat.format($reservation.totalPrice).


Defining Velocity Views for Use in an app

The following example from the /WEB-INF/classes/views.properties file defines a Velocity view bean for the sample page discussed in . Note that it sets the two format helper properties to true, as this page needs to display the performance date and ticket costing:

 showReservation.class=
 com.interface21.web.servlet.view.velocity.VelocityView
 showReservation.templateName=showReservation.vm
 showReservation.exposeDateFormatter=true
 showReservation.exposeCurrencyFormatter=true


The VelocityView class exposes the following bean properties, beyond those inherited from AbstractView:

JaVa
   
Comments