One of the areas in which Spring excels is in the separation of view technologies from the rest of the MVC framework. For example, deciding to use Velocity or XSLT in place of an existing JSP is primarily a matter of configuration. This chapter covers the major view technologies that work with Spring and touches briefly on how to add new ones. This chapter assumes you are already familiar with Section 12.5, “Views and resolving them” which covers the basics of how views in general are coupled to the MVC framework.
Spring provides a couple of out-of-the-box solutions for JSP and JSTL views. Using JSP or JSTL is done using a normal viewresolver defined in the WebApplicationContext. Furthermore, of course you need to write some JSPs that will actually render the view. This part describes some of the additional features Spring provides to facilitate JSP development.
Just as with any other view technology you're integrating with Spring, for JSPs you'll need a view resolver that will resolve your views. The most commonly used view resolvers when developing with JSPs are the InternalResourceViewResolver and the ResourceBundleViewResolver. Both are declared in the WebApplicationContext:
# The ResourceBundleViewResolver: <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename"><value>views</value></property> </bean> # And a sample properties file is uses (views.properties in WEB-INF/classes): welcome.class=org.springframework.web.servlet.view.JstlView welcome.url=/WEB-INF/jsp/welcome.jsp productList.class=org.springframework.web.servlet.view.JstlView productList.url=/WEB-INF/jsp/productlist.jsp
As you can see, the ResourceBundleViewResolver needs a properties file defining the view names mapped to 1) a class and 2) a URL. With a ResourceBundleViewResolver you can mix different types of views using only one resolver.
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property> <property name="prefix"><value>/WEB-INF/jsp/</value></property> <property name="suffix"><value>.jsp</value></property> </bean>
The InternalResourceBundleViewResolver can be configured for using JSPs as described above. As a best practice, we strongly encourage placing your JSP files in a a directory under the WEB-INF directory, so there can be no direct access by clients.
When using Java Standard Tag Library you must use a special view class, the JstlView, as JSTL needs some preparation before things such as the i18N features will work.
Spring provides data binding of request parameters to command objects as described in earlier chapters. To facilitate the development of JSP pages in combination with those data binding features, Spring provides a few tags that make things even easier. All Spring tags have html escaping features to enable or disable escaping of characters.
The tag library descriptor (TLD) is included in the spring.jar as well in the distribution itself. More information about the individual tags can be found online: http://www.springframework.org/docs/taglib/index.html.
It is possible to integrate Tiles - just as any other view technology - in web applications using Spring. The following describes in a broad way how to do this.
To be able to use Tiles you have to have a couple of additional dependencies included in your project. The following is the list of dependencies you need.
struts version 1.1
commons-beanutils
commons-digester
commons-logging
commons-lang
The dependencies are all available in the Spring distribution.
To be able to use Tiles, you have to configure it using files containing definitions (for basic information on definitions and other Tiles concepts, please have a look at http://jakarta.apache.org/struts). In Spring this is done using the TilesConfigurer. Have a look at the following piece of example ApplicationContext configuration:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles.TilesConfigurer"> <property name="factoryClass"> <value>org.apache.struts.tiles.xmlDefinition.I18nFactorySet</value> </property> <property name="definitions"> <list> <value>/WEB-INF/defs/general.xml</value> <value>/WEB-INF/defs/widgets.xml</value> <value>/WEB-INF/defs/administrator.xml</value> <value>/WEB-INF/defs/customer.xml</value> <value>/WEB-INF/defs/templates.xml</value> </list> </property> </bean>
As you can see, there are five files containing definitions, which are all located in the WEB-INF/defs directory. At initialization of the WebApplicationContext, the files will be loaded and the definitionsfactory defined by the factoryClass-property is initialized. After that has been done, the tiles includes in the definition files can be used as views within your Spring web application. To be able to use the views you have to have a ViewResolver just as with any other view technology used with Spring. Below you can find two possibilities, the InternalResourceViewResolver and the ResourceBundleViewResolver.
The InternalResourceViewResolver instantiates the given viewClass for each view it has to resolve.
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="requestContextAttribute"><value>requestContext</value></property> <property name="viewClass"> <value>org.springframework.web.servlet.view.tiles.TilesView</value> </property> </bean>
The ResourceBundleViewResolver has to be provided with a property file containing viewnames and viewclasses the resolver can use:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename"><value>views</value></property> </bean>
... welcomeView.class=org.springframework.web.servlet.view.tiles.TilesView welcomeView.url=welcome (<b>this is the name of a definition</b>) vetsView.class=org.springframework.web.servlet.view.tiles.TilesView vetsView.url=vetsView (<b>again, this is the name of a definition</b>) findOwnersForm.class=org.springframework.web.servlet.view.JstlView findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp ...
As you can see, when using the ResourceBundleViewResolver, you can mix view using different view technologies.
Velocity and FreeMarker are two templating languages that can both be used as view technologies within Spring MVC applications. The languages are quite similar and serve similar needs and so are considered together in this section. For semantic and syntactic differences between the two languages, see the FreeMarker web site.
Your web application will need to include velocity-1.x.x.jar or freemarker-2.x.jar in order to work with Velocity or FreeMarker respectively and commons-collections.jar needs also to be available for Velocity. Typically they are included in the WEB-INF/lib folder where they are guaranteed to be found by a J2EE server and added to the classpath for your application. It is of course assumed that you already have the spring.jar in your WEB-INF/lib folder too! The latest stable velocity, freemarker and commons collections jars are supplied with the Spring framework and can be copied from the relevant /lib/ sub-directories. If you make use of Spring's dateToolAttribute or numberToolAttribute in your Velocity views, you will also need to include the velocity-tools-generic-1.x.jar
A suitable configuration is initialized by adding the relevant configurer bean definition to your *-servlet.xml as shown below:
<!-- This bean sets up the Velocity environment for us based on a root path for templates. Optionally, a properties file can be specified for more control over the Velocity environment, but the defaults are pretty sane for file based template loading. --> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath"><value>/WEB-INF/velocity/</value></property> </bean> <!-- View resolvers can also be configured with ResourceBundles or XML files. If you need different view resolving based on Locale, you have to use the resource bundle resolver. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="cache"><value>true</value></property> <property name="prefix"><value></value></property> <property name="suffix"><value>.vm</value></property> </bean>
<!-- freemarker config --> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath"><value>/WEB-INF/freemarker/</value></property> </bean> <!-- View resolvers can also be configured with ResourceBundles or XML files. If you need different view resolving based on Locale, you have to use the resource bundle resolver. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="cache"><value>true</value></property> <property name="prefix"><value></value></property> <property name="suffix"><value>.ftl</value></property> </bean>
NB: For non web-apps add a VelocityConfigurationFactoryBean or a FreeMarkerConfigurationFactoryBean to your application context definition file.
Your templates need to be stored in the directory specified by the *Configurer bean shown above in Section 13.4.2, “Context configuration” This document does not cover details of creating templates for the two languages - please see their relevant websites for information. If you use the view resolvers highlighted, then the logical view names relate to the template file names in similar fashion to InternalResourceViewResolver for JSP's. So if your controller returns a ModelAndView object containing a view name of "welcome" then the resolvers will look for the /WEB-INF/freemarker/welcome.ftl or /WEB-INF/velocity/welcome.vm template as appropriate.
The basic configurations highlighted above will be suitable for most application requirements, however additional configuration options are available for when unusual or advanced requirements dictate.
This file is completely optional, but if specified, contains the values that are passed to the Velocity runtime in order to configure velocity itself. Only required for advanced configurations, if you need this file, specify its location on the VelocityConfigurer bean definition above.
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="configLocation"> <value>/WEB-INF/velocity.properties</value> </property> </bean>
Alternatively, you can specify velocity properties directly in the bean definition for the Velocity config bean by replacing the "configLocation" property with the following inline properties.
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="velocityProperties"> <props> <prop key="resource.loader">file</prop> <prop key="file.resource.loader.class"> org.apache.velocity.runtime.resource.loader.FileResourceLoader </prop> <prop key="file.resource.loader.path">${webapp.root}/WEB-INF/velocity</prop> <prop key="file.resource.loader.cache">false</prop> </props> </property> </bean>
Refer to the API documentation for Spring configuration of Velocity, or the Velocity documentation for examples and definitions of the velocity.properties file itself.
FreeMarker 'Settings' and 'SharedVariables' can be passed directly to the FreeMarker Configuration object managed by Spring by setting the appropriate bean properties on the FreeMarkerConfigurer bean. The freemarkerSettings property requires a java.util.Properties object and the freemarkerVariables property requires a java.util.Map.
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath"><value>/WEB-INF/freemarker/</value></property> <property name="freemarkerVariables"> <map> <entry key="xml_escape"><ref local="fmXmlEscape"/></entry> </map> </property> </bean> <bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>
See the FreeMarker documentation for details of settings and variables as they apply to the Configuration object.
Spring provides a tag library for use in JSP's that contains (amongst other things) a <spring:bind> tag. This tag primarily enables forms to display values from form backing objects and to show the results of failed validations from a Validator in the web or business tier. From version 1.1, Spring now has support for the same functionality in both Velocity and FreeMarker, with additional convenience macros for generating form input elements themselves.
A standard set of macros are maintained within the spring.jar file for both languages, so they are always available to a suitably configured application. However they can only be used if your view sets the bean property exposeSpringMacroHelpers to true . The same property can be set on VelocityViewResolver or FreeMarkerViewResolver too if you happen to be using it, in which case all of your views will inherit the value from it. Note that this property is not required for any aspect of HTML form handling except where you wish to take advantage of the Spring macros. Below is an example of a view.properties file showing correct configuration of such a view for either language;
personFormV.class=org.springframework.web.servlet.view.velocity.VelocityView personFormV.url=personForm.vm personFormV.exposeSpringMacroHelpers=true
personFormF.class=org.springframework.web.servlet.view.freemarker.FreeMarkerView personFormF.url=personForm.ftl personFormF.exposeSpringMacroHelpers=true
Some of the macros defined in the Spring libraries are considered internal (private) but no such scoping exists in the macro definitions making all macros visible to calling code and user templates. The following sections concentrate only on the macros you need to be directly calling from within your templates. If you wish to view the macro code directly, the files are called spring.vm / spring.ftl and are in the packages org.springframework.web.servlet.view.velocity or org.springframework.web.servlet.view.freemarker respectively.
In your html forms (vm / ftl templates) that act as the 'formView' for a Spring form controller, you can use code similar to the following to bind to field values and display error messages for each input field in similar fashion to the JSP equivalent. Note that the name of the command object is "command" by default, but can be overridden in your MVC configuration by setting the 'commandName' bean property on your form controller. Example code is shown below for the personFormV and personFormF views configured earlier;
<!-- velocity macros are automatically available --> <html> ... <form action="" method="POST"> Name: #springBind( "command.name" ) <input type="text" name="${status.expression}" value="$!status.value" /><br> #foreach($error in $status.errorMessages) <b>$error</b> <br> #end <br> ... <input type="submit" value="submit"/> </form> ... </html>
<!-- freemarker macros have to be imported into a namespace. We strongly recommend sticking to 'spring' --> <#import "spring.ftl" as spring /> <html> ... <form action="" method="POST"> Name: <@spring.bind "command.name" /> <input type="text" name="${spring.status.expression}" value="${spring.status.value?default("")}" /><br> <#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list> <br> ... <input type="submit" value="submit"/> </form> ... </html>
#springBind / <@spring.bind> requires a 'path' argument which consists of the name of your command object (it will be 'command' unless you changed it in your FormController properties) followed by a period and the name of the field on the command object you wish to bind to. Nested fields can be used too such as "command.address.street". The bind macro assumes the default HTML escaping behavior specified by the ServletContext parameter defaultHtmlEscape in web.xml
The optional form of the macro called #springBindEscaped / <@spring.bindEscaped> takes a second argument and explicitly specifies whether HTML escaping should be used in the status error messages or values. Set to true or false as required. Additional form handling macros simplify the use of HTML escaping and these macros should be used wherever possible. They are explained in the next section.
Additional convenience macros for both languages simplify both binding and form generation (including validation error display). It is never necessary to use these macros to generate form input fields, and they can be mixed and matched with simple HTML or calls direct to the spring bind macros highlighted previously.
The following table of available macros show the VTL and FTL definitions and the parameter list that each takes.
Table 13.1. table of macro definitions
macro | VTL definition | FTL definition |
---|---|---|
message (output a string from a resource bundle based on the code parameter) | #springMessage($code) | <@spring.message code/> |
messageText (output a string from a resource bundle based on the code parameter, falling back to the value of the default parameter) | #springMessageText($code $default) | <@spring.messageText code, default/> |
url (prefix a relative URL with the application's context root) | #springUrl($relativeUrl) | <@spring.url relativeUrl/> |
formInput (standard input field for gathering user input) | #springFormInput($path $attributes) | <@spring.formInput path, attributes, fieldType/> |
formHiddenInput * (hidden input field for submitting non-user input) | #springFormHiddenInput($path $attributes) | <@spring.formHiddenInput path, attributes/> |
formPasswordInput * (standard input field for gathering passwords. Note that no value will ever be populated in fields of this type) | #springFormPasswordInput($path $attributes) | <@spring.formPasswordInput path, attributes/> |
formTextarea (large text field for gathering long, freeform text input) | #springFormTextarea($path $attributes) | <@spring.formTextarea path, attributes/> |
formSingleSelect (drop down box of options allowing a single required value to be selected) | #springFormSingleSelect( $path $options $attributes) | <@spring.formSingleSelect path, options, attributes/> |
formMultiSelect (a list box of options allowing the user to select 0 or more values) | #springFormMultiSelect($path $options $attributes) | <@spring.formMultiSelect path, options, attributes/> |
formRadioButtons (a set of radio buttons allowing a single selection to be made from the available choices) | #springFormRadioButtons($path $options $separator $attributes) | <@spring.formRadioButtons path, options separator, attributes/> |
formCheckboxes (a set of checkboxes allowing 0 or more values to be selected) | #springFormCheckboxes($path $options $separator $attributes) | <@spring.formCheckboxes path, options, separator, attributes/> |
showErrors (simplify display of validation errors for the bound field) | #springShowErrors($separator $classOrStyle) | <@spring.showErrors separator, classOrStyle/> |
* In FTL (FreeMarker), these two macros are not actually required as you can use the normal formInput macro, specifying 'hidden' or 'password' as the value for the fieldType parameter.
The parameters to any of the above macros have consistent meanings:
path: the name of the field to bind to (ie "command.name")
options: a Map of all the available values that can be selected from in the input field. The keys to the map represent the values that will be POSTed back from the form and bound to the command object. Map objects stored against the keys are the labels displayed on the form to the user and may be different from the corresponding values posted back by the form. Usually such a map is supplied as reference data by the controller. Any Map implementation can be used depending on required behavior. For strictly sorted maps, a SortedMap such as a TreeMap with a suitable Comparator may be used and for arbitrary Maps that should return values in insertion order, use a LinkedHashMap or a LinkedMap from commons-collections.
separator: where multiple options are available as discreet elements (radio buttons or checkboxes), the sequence of characters used to separate each one in the list (ie "<br>").
attributes: an additional string of arbitrary tags or text to be included within the HTML tag itself. This string is echoed literally by the macro. For example, in a textarea field you may supply attributes as 'rows="5" cols="60"' or you could pass style information such as 'style="border:1px solid silver"'.
classOrStyle: for the showErrors macro, the name of the CSS class that the span tag wrapping each error will use. If no information is supplied (or the value is empty) then the errors will be wrapped in <b></b> tags.
Examples of the macros are outlined below some in FTL and some in VTL. Where usage differences exist between the two languages, they are explained in the notes.
<!-- the Name field example from above using form macros in VTL --> ... Name: #springFormInput("command.name" "")<br> #springShowErrors("<br>" "")<br>
The formInput macro takes the path parameter (command.name) and an additional attributes parameter which is empty in the example above. The macro, along with all other form generation macros, performs an implicit spring bind on the path parameter. The binding remains valid until a new bind occurs so the showErrors macro doesn't need to pass the path parameter again - it simply operates on whichever field a bind was last created for.
The showErrors macro takes a separator parameter (the characters that will be used to separate multiple errors on a given field) and also accepts a second parameter, this time a class name or style attribute. Note that FreeMarker is able to specify default values for the attributes parameter, unlike Velocity, and the two macro calls above could be expressed as follows in FTL:
<@spring.formInput "command.name"/> <@spring.showErrors "<br>"/>
Output is shown below of the form fragment generating the name field, and displaying a validation error after the form was submitted with no value in the field. Validation occurs through Spring's Validation framework.
The generated HTML looks like this:
Name: <input type="text" name="name" value="" > <br> <b>required</b> <br> <br>
The formTextarea macro works the same way as the formInput macro and accepts the same parameter list. Commonly, the second parameter (attributes) will be used to pass style information or rows and cols attributes for the textarea.
Four selection field macros can be used to generate common UI value selection inputs in your HTML forms.
formSingleSelect
formMultiSelect
formRadioButtons
formCheckboxes
Each of the four macros accepts a Map of options containing the value for the form field, and the label corresponding to that value. The value and the label can be the same.
An example of radio buttons in FTL is below. The form backing object specifies a default value of 'London' for this field and so no validation is necessary. When the form is rendered, the entire list of cities to choose from is supplied as reference data in the model under the name 'cityMap'.
... Town: <@spring.formRadioButtons "command.address.town", cityMap, "" /><br><br>
This renders a line of radio buttons, one for each value in cityMap using the separator "". No additional attributes are supplied (the last parameter to the macro is missing). The cityMap uses the same String for each key-value pair in the map. The map's keys are what the form actually submits as POSTed request parameters, map values are the labels that the user sees. In the example above, given a list of three well known cities and a default value in the form backing object, the HTML would be
Town: <input type="radio" name="address.town" value="London" > London <input type="radio" name="address.town" value="Paris" checked="checked" > Paris <input type="radio" name="address.town" value="New York" > New York
If your application expects to handle cities by internal codes for example, the map of codes would be created with suitable keys like the example below.
protected Map referenceData(HttpServletRequest request) throws Exception { Map cityMap = new LinkedHashMap(); cityMap.put("LDN", "London"); cityMap.put("PRS", "Paris"); cityMap.put("NYC", "New York"); Map m = new HashMap(); m.put("cityMap", cityMap); return m; }
The code would now produce output where the radio values are the relevant codes but the user still sees the more user friendly city names.
Town: <input type="radio" name="address.town" value="LDN" > London <input type="radio" name="address.town" value="PRS" checked="checked" > Paris <input type="radio" name="address.town" value="NYC" > New York
Default usage of the form macros above will result in HTML tags that are HTML 4.01 compliant and that use the default value for HTML escaping defined in your web.xml as used by Spring's bind support. In order to make the tags XHTML compliant or to override the default HTML escaping value, you can specify two variables in your template (or in your model where they will be visible to your templates). The advantage of specifying them in the templates is that they can be changed to different values later in the template processing to provide different behavior for different fields in your form.
To switch to XHTML compliance for your tags, specify a value of 'true' for a model/context variable named xhtmlCompliant:
## for Velocity.. #set($springXhtmlCompliant = true) <#-- for FreeMarker --> <#assign xhtmlCompliant = true in spring>
Any tags generated by the Spring macros will now be XHTML compliant after processing this directive.
In similar fashion, HTML escaping can be specified per field:
<#-- until this point, default HTML escaping is used --> <#assign htmlEscape = true in spring> <#-- next field will use HTML escaping --> <@spring.formInput "command.name" /> <#assign htmlEscape = false in spring> <#-- all future fields will be bound with HTML escaping off -->
XSLT is a transformation language for XML and is popular as a view technology within web applications. XSLT can be a good choice as a view technology if your application naturally deals with XML, or if your model can easily be converted to XML. The following section shows how to produce an XML document as model data and have it transformed with XSLT in a Spring application.
This example is a trivial Spring application that creates a list of words in the Controller and adds them to the model map. The map is returned along with the view name of our XSLT view. See Section 12.3, “Controllers” for details of Spring Controllers. The XSLT view will turn the list of words into a simple XML document ready for transformation.
Configuration is standard for a simple Spring application. The dispatcher servlet config file contains a reference to a ViewResolver, URL mappings and a single controller bean..
<bean id="homeController"class="xslt.HomeController"/>
..that implements our word generation 'logic'.
The controller logic is encapsulated in a subclass of AbstractController, with the handler method being defined like so..
protected ModelAndView handleRequestInternal( HttpServletRequest req, HttpServletResponse resp) throws Exception { Map map = new HashMap(); List wordList = new ArrayList(); wordList.add("hello"); wordList.add("world"); map.put("wordList", wordList); return new ModelAndView("home", map); }
So far we've done nothing that's XSLT specific. The model data has been created in the same way as you would for any other Spring MVC application. Depending on the configuration of the application now, that list of words could be rendered by JSP/JSTL by having them added as request attributes, or they could be handled by Velocity by adding the object to the VelocityContext. In order to have XSLT render them, they of course have to be converted into an XML document somehow. There are software packages available that will automatically 'domify' an object graph, but within Spring, you have complete flexibility to create the DOM from your model in any way you choose. This prevents the transformation of XML playing too great a part in the structure of your model data which is a danger when using tools to manage the domification process.
In order to create a DOM document from our list of words or any other model data, we subclass org.springframework.web.servlet.view.xslt.AbstractXsltView. In doing so, we must implement the abstract method createDomNode(). The first parameter passed to this method is our model Map. Here's the complete listing of the HomePage class in our trivial word application - it uses JDOM to build the XML document before converting it to the required W3C Node, but this is simply because I find JDOM (and Dom4J) easier API's to handle than the W3C API.
package xslt; // imports omitted for brevity public class HomePage extends AbstractXsltView { protected Node createDomNode( Map model, String rootName, HttpServletRequest req, HttpServletResponse res ) throws Exception { org.jdom.Document doc = new org.jdom.Document(); Element root = new Element(rootName); doc.setRootElement(root); List words = (List) model.get("wordList"); for (Iterator it = words.iterator(); it.hasNext();) { String nextWord = (String) it.next(); Element e = new Element("word"); e.setText(nextWord); root.addContent(e); } // convert JDOM doc to a W3C Node and return return new DOMOutputter().output( doc ); } }
A series of parameter name/value pairs can optionally be defined by your subclass which will be added to the transformation object. The parameter names must match those defined in your XSLT template declared with <xsl:param name="myParam">defaultValue</xsl:param> To specify the parameters, override the method getParameters() from AbstractXsltView and return a Map of the name/value pairs. If your parameters need to derive information from the current request, you can (from version 1.1) override the getParameters(HttpServletRequest request) method instead.
Unlike JSTL and Velocity, XSLT has relatively poor support for locale based currency and date formatting. In recognition of the fact, Spring provides a helper class that you can use from within your createDomNode() methods to get such support. See the javadocs for org.springframework.web.servlet.view.xslt.FormatHelper
The views.properties file (or equivalent xml definition if you're using an XML based view resolver as we did in the Velocity examples above) looks like this for the one-view application that is 'My First Words'..
home.class=xslt.HomePage home.stylesheetLocation=/WEB-INF/xsl/home.xslt home.root=words
Here, you can see how the view is tied in with the HomePage class just written which handles the model domification in the first property '.class'. The stylesheetLocation property obviously points to the XSLT file which will handle the XML transformation into HTML for us and the final property '.root' is the name that will be used as the root of the XML document. This gets passed to the HomePage class above in the second parameter to the createDomNode method.
Finally, we have the XSLT code used for transforming the above document. As highlighted in the views.properties file, it is called home.xslt and it lives in the war file under WEB-INF/xsl.
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text/html" omit-xml-declaration="yes"/> <xsl:template match="/"> <html> <head><title>Hello!</title></head> <body> <h1>My First Words</h1> <xsl:for-each select="wordList/word"> <xsl:value-of select="."/><br /> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet>
A summary of the files discussed and their location in the WAR file is shown in the simplified WAR structure below.
ProjectRoot | +- WebContent | +- WEB-INF | +- classes | | | +- xslt | | | | | +- HomePageController.class | | +- HomePage.class | | | +- views.properties | +- lib | | | +- spring.jar | +- xsl | | | +- home.xslt | +- frontcontroller-servlet.xml
You will also need to ensure that an XML parser and an XSLT engine are available on the classpath. JDK 1.4 provides them by default, and most J2EE containers will also make them available by default, but it's a possible source of errors to be aware of.
Returning an HTML page isn't always the best way for the user to view the model output, and Spring makes it simple to generate a PDF document or an Excel spreadsheet dynamically from the model data. The document is the view and will be streamed from the server with the correct content type to (hopefully) enable the client PC to run their spreadsheet or PDF viewer application in response.
In order to use Excel views, you need to add the 'poi' library to your classpath, and for PDF generation, the iText.jar. Both are included in the main Spring distribution.
Document based views are handled in an almost identical fashion to XSLT views, and the following sections build upon the previous one by demonstrating how the same controller used in the XSLT example is invoked to render the same model as both a PDF document and an Excel spreadsheet (which can also be viewed or manipulated in Open Office).
Firstly, let's amend the views.properties file (or xml equivalent) and add a simple view definition for both document types. The entire file now looks like this with the XSLT view shown from earlier..
home.class=xslt.HomePage home.stylesheetLocation=/WEB-INF/xsl/home.xslt home.root=words xl.class=excel.HomePage pdf.class=pdf.HomePage
If you want to start with a template spreadsheet to add your model data to, specify the location as the 'url' property in the view definition
The controller code we'll use remains exactly the same from the XSLT example earlier other than to change the name of the view to use. Of course, you could be clever and have this selected based on a URL parameter or some other logic - proof that Spring really is very good at decoupling the views from the controllers!
Exactly as we did for the XSLT example, we'll subclass suitable abstract classes in order to implement custom behavior in generating our output documents. For Excel, this involves writing a subclass of org.springframework.web.servlet.view.document.AbstractExcelView and implementing the buildExcelDocument
Here's the complete listing for our Excel view which displays the word list from the model map in consecutive rows of the first column of a new spreadsheet..
package excel; // imports omitted for brevity public class HomePage extends AbstractExcelView { protected void buildExcelDocument( Map model, HSSFWorkbook wb, HttpServletRequest req, HttpServletResponse resp) throws Exception { HSSFSheet sheet; HSSFRow sheetRow; HSSFCell cell; // Go to the first sheet // getSheetAt: only if wb is created from an existing document //sheet = wb.getSheetAt( 0 ); sheet = wb.createSheet("Spring"); sheet.setDefaultColumnWidth((short)12); // write a text at A1 cell = getCell( sheet, 0, 0 ); setText(cell,"Spring-Excel test"); List words = (List ) model.get("wordList"); for (int i=0; i < words.size(); i++) { cell = getCell( sheet, 2+i, 0 ); setText(cell, (String) words.get(i)); } } }
If you now amend the controller such that it returns xl as the name of the view (return new ModelAndView("xl", map);) and run your application again, you should find that the Excel spreadsheet is created and downloaded automagically when you request the same page as before.
The PDF version of the word list is even simpler. This time, the class extends org.springframework.web.servlet.view.document.AbstractPdfView and implements the buildPdfDocument() method as follows..
package pdf; // imports omitted for brevity public class PDFPage extends AbstractPdfView { protected void buildPdfDocument( Map model, Document doc, PdfWriter writer, HttpServletRequest req, HttpServletResponse resp) throws Exception { List words = (List) model.get("wordList"); for (int i=0; i<words.size(); i++) doc.add( new Paragraph((String) words.get(i))); } }
Once again, amend the controller to return the pdf view with a return new ModelAndView("pdf", map); and reload the URL in your application. This time a PDF document should appear listing each of the words in the model map.
JasperReports (http://jasperreports.sourceforge.net) is a powerful, open-source reporting engine that supports the creation of report designs using an easily understood XML file formats. JasperReports is capable of rendering reports output into four different formats: CSV, Excel, HTML and PDF.
Your application will need to include the latest release of JasperReports, which at the time of writing was 0.6.1. JasperReports itself depends on the following projects:
BeanShell
Commons BeanUtils
Commons Collections
Commons Digester
Commons Logging
iText
POI
JasperReports also requires a JAXP compliant XML parser.
To configure JasperReports views in your ApplicationContext you have to define a ViewResolver to map view names to the appropriate view class depending on which format you want your report rendered in.
Typically, you will use the ResourceBundleViewResolver to map view names to view classes and files in a properties file
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename"> <value>views</value> </property> </bean>
Here we've configured an instance of ResourceBundleViewResolver which will look for view mappings in the resource bundle with base name views. The exact contents of this file is described in the next section.
Spring contains five different View implementations for JasperReports four of which corresponds to one of the four output formats supported by JasperReports and one that allows for the format to be determined at runtime:
Table 13.2. JasperReports View Classes
Class Name | Render Format |
---|---|
JasperReportsCsvView | CSV |
JasperReportsHtmlView | HTML |
JasperReportsPdfView | |
JasperReportsXlsView | Microsoft Excel |
JasperReportsMultiFormatView | Decided at runtime (see Section 13.7.2.4, “Using JasperReportsMultiFormatView”) |
Mapping one of these classes to a view name and a report file is simply a matter of adding the appropriate entries into the resource bundle configured in the previous section as shown here:
simpleReport.class=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper
Here you can see that the view with name, simpleReport, is mapped to the JasperReportsPdfView class. This will cause the output of this report to be rendered in PDF format. The url property of the view is set to the location of the underlying report file.
JasperReports has two distinct types of report file: the design file, which has a .jrxml extension, and the compiled report file, which has a .jasper extension. Typically, you use the JasperReports Ant task to compile your .jrxml design file into a .jasper file before deploying it into your application. With Spring you can map either of these files to your report file and Spring will take care of compiling the .jrxml file on the fly for you. You should note that after a .jrxml file is compiled by Spring, the compiled report is cached for the life of the application. To make changes to the file you will need to restart your application.
The JasperReportsMultiFormatView allows for report format to be specified at runtime. The actual rendering of the report is delegated to one of the other JasperReports view classes - the JasperReportsMultiFormatView class simply adds a wrapper layer that allows for the exact implementation to be specified at runtime.
The JasperReportsMultiFormatView class introduces two concepts: the format key and the discriminator key. The JasperReportsMultiFormatView class uses the mapping key to lookup the actual view implementation class and uses the format key to lookup up the mapping key. From a coding perspective you add an entry to your model with the formay key as the key and the mapping key as the value, for example:
public ModelAndView handleSimpleReportMulti(HttpServletRequest request, HttpServletResponse response) throws Exception { String uri = request.getRequestURI(); String format = uri.substring(uri.lastIndexOf(".") + 1); Map model = getModel(); model.put("format", format); return new ModelAndView("simpleReportMulti", model); }
In this example, the mapping key is determined from the extension of the request URI and is added to the model under the default format key: format. If you wish to use a different format key then you can configure this using the formatKey property of the JasperReportsMultiFormatView class.
By default the following mapping key mappings are configured in JasperReportsMultiFormatView:
Table 13.3. JasperReportsMultiFormatView Default Mapping Key Mappings
Mapping Key | View Class |
---|---|
csv | JasperReportsCsvView |
html | JasperReportsHtmlView |
JasperReportsPdfView | |
xls | JasperReportsXlsView |
So in the example above a request to URI /foo/myReport.pdf would be mapped to the JasperReportsPdfView class. You can override the mapping key to view class mappings using the formatMappings property of JasperReportsMultiFormatView.
In order to render your report correctly in the format you have chosen, you must supply Spring with all of the data needed to populate your report. For JasperReports this means you must pass in all report parameters along with the report datasource. Report parameters are simple name/value pairs and can be added be to the Map for your model as you would add any name/value pair.
When adding the datasource to the model you have two approaches to choose from. The first approach is to add an instance of JRDataSource or Collection to the model Map under any arbitrary key. Spring will then locate this object in the model and treat it as the report datasource. For example, you may populate your model like this:
private Map getModel() { Map model = new HashMap(); Collection beanData = getBeanData(); model.put("myBeanData", beanData); return model; }
The second approach is to add the instance of JRDataSource or Collection under a specific key and then configure this key using the reportDataKey property of the view class. In both cases Spring will instances of Collection in a JRBeanCollectionDataSource instance. For example:
private Map getModel() { Map model = new HashMap(); Collection beanData = getBeanData(); Collection someData = getSomeData(); model.put("myBeanData", beanData); model.put("someData", someData); return model; }
Here you can see that two Collection instances are being added to the model. To ensure that the correct one is used, we simply modify our view configuration as appropriate:
simpleReport.class=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper simpleReport.reportDataKey=myBeanData
Be aware that when using the first approach, Spring will use the first instance of JRDataSource or Collection that it encounters. If you need to place multiple instances of JRDataSource or Collection into the model then you need to use the second approach.
JasperReports provides support for embedded sub-reports within your master report files. There are a wide variety of mechanisms for including sub-reports in your report files. The easiest way is to hard code the report path and the SQL query for the sub report into your design files. The drawback of this approach is obvious - the values are hard-coded into your report files reducing reusability and making it harder to modify and update report designs. To overcome this you can configure sub-reports declaratively and you can include additional data for these sub-reports directly from your controllers.
To control which sub-report files are included in a master report using Spring, your report file must be configured to accept sub-reports from an external source. To do this you declare a parameter in your report file like this:
<parameter name="ProductsSubReport" class="net.sf.jasperreports.engine.JasperReport"/>
Then, you define your sub-report to use this sub-report parameter:
<subreport> <reportElement isPrintRepeatedValues="false" x="5" y="25" width="325" height="20" isRemoveLineWhenBlank="true" backcolor="#ffcc99"/> <subreportParameter name="City"> <subreportParameterExpression><![CDATA[$F{city}]]></subreportParameterExpression> </subreportParameter> <dataSourceExpression><![CDATA[$P{SubReportData}]]></dataSourceExpression> <subreportExpression class="net.sf.jasperreports.engine.JasperReport"> <![CDATA[$P{ProductsSubReport}]]></subreportExpression> </subreport>
This defines a master report file that expects the sub-report to be passed in as an instance of net.sf.jasperreports.engine.JasperReports under the parameter ProductsSubReport. When configuring your Jasper view class, you can instruct Spring to load a report file and pass into the JasperReports engine as a sub-report using the subReportUrls property:
<property name="subReportUrls"> <map> <entry key="ProductsSubReport"> <value>/WEB-INF/reports/subReportChild.jrxml</value> </entry> </map> </property>
Here, the key of the Map corresponds to the name of the sub-report parameter in th report design file, and the entry is the URL of the report file. Spring will load this report file, compiling it if necessary, and will pass into the JasperReports engine under the given key.
This step is entirely optional when using Spring configure your sub-reports. If you wish, you can still configure the data source for your sub-reports using static queries. However, if you want Spring to convert data returned in your ModelAndView into instances of JRDataSource then you need to specify which of the parameters in your ModelAndView Spring should convert. To do this configure the list of parameter names using the subReportDataKeys property of the your chosen view class:
<property name="subReportDataKeys"> <value>SubReportData</value> </property>
Here, the key you supply MUST correspond to both the key used in your ModelAndView and the key used in your report design file.
If you have special requirements for exporter configuration - perhaps you want a specific page size for your PDF report, then you can configure these exporter parameters declaratively in your Spring configuration file using the exporterParameters property of the view class. The exporterParameters property is typed as Map and in your configuration the key of an entry should be the fully-qualified name of a static field that contains the exporter parameter definition and the value of an entry should be the value you want to assign to the parameter. An example of this is shown below:
<bean id="htmlReport" class="org.springframework.web.servlet.view.jasperreports.JasperReportsHtmlView"> <property name="url"> <value>/WEB-INF/reports/simpleReport.jrxml</value> </property> <property name="exporterParameters"> <map> <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER"> <value>Footer by Spring! </td><td width="50%">&nbsp; </td></tr> </table></body></html> </value> </entry> </map> </property> </bean>
Here you can see that the JasperReportsHtmlView is being configured with an exporter parameter for net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER which will output a footer in the resulting HTML.