On May 29, 2011 wrote: Content Negotiation with Spring 3 MVC & Velocity
One of my pet subjects is content negotiation. There are many benefits to a RESTful approach to web application architecture but few which fill me with such a curious delight as the spectacle of seeing loads of different representations of the same underlying resource being returned at the flick of an Accept header. That probably makes me a very weird and geeky person but what the hell: content negotiation is fun! … especially when it is achieved with practically zero effort.
Anyhow, some of the approaches I have seen to content negotiation seem a little “overweight”, frequently involving significant amounts of coding in order to add support for new representations. To be fair, they are intended to provide über solutions to every conceivable scenario — which is great when you encounter one of those 1%-of-the-time situations. Normally, however, you will simply be returning a new text-based format. Surely that should be a breeze, right? Well, yes and no.
One of the great improvements to Spring in version 3 is its support for REST and content negotiation in particular. It also provides almost-out-of-the-box solutions for producing JSON and XML representations of a view model that might otherwise be rendered as HTML (i.e. when we don’t ask for either JSON or XML specifically). But sometimes they don’t quite give you the results you want and customisation of the output is nigh-on impossible, so you have to go back to writing some code. Which is a pain. But hold on a second … am I not already using a mechanism for serializing a Java object graph as text in order to produce HTML pages? Indeed I am. In my case, in my hobby project which I call “Apposite” (see my post on end-to-end BDD testing), I am using Apache Velocity
In the post on BDD testing, I setup a single test case which asserted that a “administrator” could log in to an “administration dashboard”. Let’s take up that case and try not just to make it pass but also to make the resulting page viewable in a number of different formats (e.g. atom, rss, xml, json, plain text etc). Really, I should be writing BDD tests specifically to cover each of these “view as format” scenarios but, as they are not really part of the application requirements and I am only doing it for demonstration purposes, I am going to skip that for now.
Once again, let’s take this step-by-step and begin by setting up our basic Spring web application. First off, we need to add the Spring dependencies to the pom.xml, if not already present:
<properties>
<spring.version>3.0.5.RELEASE</spring.version>
<spring.security.version>${spring.version}</spring.security.version>
</properties>
First up, under the DRY principle, I declare the Spring version number as a property.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.security.version}</version>
</dependency>
The Spring framework is well modularised, so there are a number of different dependencies required. It can become a little confusing when trying to determine which Spring dependencies one actually requires but this is preferable, in my view (and, obviously, the view of the people at Spring), to including a single massive jar containing tonnes of stuff you don’t need. The key modules in the list above are:
- spring-context
- Contains the Spring application context classes and annotations to enable you actually configure and create a Spring context.
- spring-core
- For the purposes of our web application, the most important aspect of the spring-core.jar is that it is the home for the new, unified conversion API introduced with Spring 3. This represents a significant improvement over the previous
PropertyEditorbased support for data binding. - spring-web
- This is obviously necessary for a web application, containing, as it does, most of the basic Servlet API integration code including the
ContextLoaderListenerand request mapping annotations. - spring-webmvc
- Contains a number important support classes and servlet API integrations for creating MVC web apps; notably the
DispatcherServletand theMvcNamespaceHandler(more on which below). Conventionally, this is also used to provide view classes, such as Spring’s Velocity and Freemarker implementations. However, I will be utilising instead my own Velocity integration library, because it is, of course, much better! (It is my spring-velocity library that makes the content negotiation we are going to be using possible so, whilst I won’t go into the implementation in detail, I will note some key differences with the usual integration with Velocity provided by Spring out-of-the-box.) - spring-security-core
- Spring Security will be used to provide user authentication services since the test case I want to pass requires this.
Because I am using my own Spring-Velocity integration library, I also need to declare my maven repository in the POM:
<repositories>
<repository>
<id>christophertownson-com-public</id>
<url>http://christophertownson.com/mvn/</url>
</repository>
</repositories>
And add the library as a dependency …
<dependency>
<groupId>com.christophertownson</groupId>
<artifactId>spring-velocity</artifactId>
<version>0.0.2</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
I am excluding commons-logging (which is picked up as a transitive dependency via Velocity and Spring) here because I am using slf4j so neither need nor want it.
Finally, I also want to use the XStreamMarshaller and MappingJacksonJsonView to produce default XML and JSON representations, respectively, so I need to declare runtime dependencies on XStream and Jackson-Mapper:
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.3.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.6.2</version>
<scope>runtime</scope>
</dependency>
Before we start, let’s remind ourselves of the test scenario:
Scenario: I cannot access the administration dashboard unless I am logged in Given I am not logged in When I go to the administration dashboard Then I am asked to login Then I enter the administrator credentials Then I am redirected to the administration dashboard
Clearly, we are going to need some kind of “admin dashboard controller” to satisfy this test, so let’s begin with that:
public class DashboardAdminControllerTest {
private DashboardAdminController controller;
@Before public void setup() {
controller = new DashboardAdminController();
}
@Test public void shouldReturnEmptyModelWhenIGetDashboard() {
assertThat(controller.getDashboard(), equalTo((Map<String, Object>) new HashMap<String, Object>()));
}
}
This simple test should be sufficient for now: all it asserts is that there is a “get dashboard” method which, for now, just returns an empty model. That should be easy enough to pass …
@Controller public class DashboardAdminController {
@RequestMapping(value = "/admin", method = RequestMethod.GET) public Map<String, Object> getDashboard() {
return new HashMap<String, Object>();
}
}
Some things to notice:
- In the creation of the first controller here, we are setting up a namespace and naming convention for “administration controllers” whereby they will live in the
org.apposite.controller.adminpackage and be named*AdminController. - The controller knows only about the model and nothing about view names (there are some unsatisfactory points to come where it will need to know about the latter and I will have to compromise with the framework, but that is another story).
- The unit test covers only what the controller, as a class, knows about (i.e. the model). The configuration (request mapping, in this case) is provided as metadata on the class and implicitly covered by the end-to-end functional test.
We’re also going to need some kind of “login controller”. This is cross-cutting functionality but is performed by a “User”, so I’m going to create a UserController for this purpose:
public class UserControllerTest {
private UserController controller;
@Before public void setup() {
controller = new UserController();
}
@Test public void shouldReturnEmptyModelWhenIGetLogin() {
assertThat(controller.login(), equalTo((Map<String, Object>) new HashMap<String, Object>()));
}
}
@Controller public class UserController extends AbstractController {
@RequestMapping(value = "/users/login", method = RequestMethod.GET) public Map<String, Object> login() {
return new HashMap<String, Object>();
}
}
Because neither controller is yet required to generate any model data, they are both extremely simple.
Next, we need to provide some way to map the request to a view template. For this purpose, Spring provides the RequestToViewNameTranslator interface. I’m obviously using TDD here (if you aren’t these days, what are you doing?!), so I begin with a test for my implementation:
public class ViewNameTranslatorTest {
private ViewNameTranslator vnt;
private MockHttpServletRequest req;
@Before public void setup() {
vnt = new ViewNameTranslator();
vnt.setUriPattern("^/((?:admin/)?[a-zA-Z0-9-_]+)(?:\\.[a-zA-Z0-9]+)?(/|/([a-zA-Z0-9-_]+)(?:\\.[a-zA-Z0-9]+)?(/|/([a-zA-Z0-9-_]+)/?)?)?(?:\\.[a-zA-Z0-9]+)?");
vnt.setEntityIdentifierPattern("^[0-9]+$");
vnt.setEntityIndex(1);
vnt.setActionIndex(3);
vnt.setEntityActionIndex(5);
vnt.setDefaultAction("list");
vnt.setReadAction("read");
}
@Test public void shouldReturnAdministratorsDashboardView() throws Exception {
givenRequest("GET", "/admin");
thenViewNameIs("/admin/list");
}
@Test public void shouldReturnLoginFormView() throws Exception {
givenRequest("GET", "/users/login");
thenViewNameIs("/users/login");
}
private void givenRequest(String method, String uri) {
givenRequest(method, "/apposite", uri);
}
private void givenRequest(String method, String contextPath, String uri) {
req = new MockHttpServletRequest(method, contextPath + uri);
req.setContextPath(contextPath);
}
private void thenViewNameIs(String name) throws Exception {
assertThat(vnt.getViewName(req), is(name));
}
}
Now, you are probably (and should be) thinking “What the hell is going on in that setup method?” I will admit that I am jumping-the-gun a little here, but it is worth doing, I think: one of my non-functional requirements for this application is that all URIs are predictable on the basis of a pattern description (i.e. a regular expression). In other words, it is conventional. This can become restrictive but, on the whole, I feel that the consistency it lends to the application is desirable from both a developer’s and a user’s perspective: “usability” is a phenomenon that can be described in terms of “intuitiveness” which, in turn, can be described as a form of pre-reflective pattern recognition. Usability is a vital consideration because, teleologically, it describes a tendency to facilitate, rather than hinder, intent and action (whether that is of a developer extending a code base or a user attempting to complete some scenario). Therefore, all my URIs will take the following form, where each element is optional:
- A namespace (e.g. “admin”)
- An entity name
- An entity identifier
- An action name (ideally, we would be able to encapsulate the concept of an “action” entirely within the use of HTTP verbs but there are occasions where it is necessary to provide URIs that include an action in order to easily support conventional human interaction via a web browser).
- A file extension (which can used to override the content-type specified by the
Acceptheader where necessary).
My ViewNameTranslator implementation will, consequently, have some URI patterns defined with capture group indexes set correspondingly so that it can parse out the relevant constituent parts of my URIs. Nonetheless, the main thing to note is that the test asserts that the view name for the URI GET /admin will be /admin/list and that, for GET /users/login, the view name will be /users/login.
@Component public class ViewNameTranslator implements RequestToViewNameTranslator {
private Integer actionIndex;
private String defaultAction;
private Integer entityActionIndex;
private RegularExpression entityIdentifierPattern;
private Integer entityIndex;
private String readAction;
private RegularExpression uriPattern;
@Override public String getViewName(HttpServletRequest request) throws Exception {
String uri = request.getRequestURI().replaceFirst(request.getContextPath(), "");
if (!uriPattern.matches(uri)) return null;
List<String> groups = uriPattern.groups(uri);
String entity = groups.get(entityIndex);
String action = groups.size() > actionIndex ? groups.get(actionIndex) : defaultAction;
String entityAction = groups.size() > entityActionIndex ? groups.get(entityActionIndex) : null;
String ext = FilenameUtils.getExtension(uri);
return "/" + entity + "/" + (entityIdentifierPattern.matches(action) ? entityAction != null ? entityAction : readAction : action) + (isNotBlank(ext) ? "." + ext : "");
}
@Autowired public void setActionIndex(@Value("${org.apposite.view.ViewNameTranslator.actionIndex}") Integer actionIndex) {
this.actionIndex = actionIndex;
}
@Autowired public void setDefaultAction(@Value("${org.apposite.view.ViewNameTranslator.defaultAction}") String defaultAction) {
this.defaultAction = defaultAction;
}
@Autowired public void setEntityActionIndex(@Value("${org.apposite.view.ViewNameTranslator.entityActionIndex}") Integer entityActionIndex) {
this.entityActionIndex = entityActionIndex;
}
@Autowired public void setEntityIdentifierPattern(@Value("${org.apposite.view.ViewNameTranslator.entityIdentifierPattern}") String entityIdentifierPattern) {
this.entityIdentifierPattern = new RegularExpression(entityIdentifierPattern, Flag.CASE_INSENSITIVE);
}
@Autowired public void setEntityIndex(@Value("${org.apposite.view.ViewNameTranslator.entityIndex}") Integer entityIndex) {
this.entityIndex = entityIndex;
}
@Autowired public void setReadAction(@Value("${org.apposite.view.ViewNameTranslator.readAction}") String readAction) {
this.readAction = readAction;
}
@Autowired public void setUriPattern(@Value("${org.apposite.view.ViewNameTranslator.uriPattern}") String uriPattern) {
this.uriPattern = new RegularExpression(uriPattern, Flag.CASE_INSENSITIVE);
}
}
The things to note about this class are:
- It is annotated with
@Component: it is a Spring bean. No bean ID is necessary. Simply by having a Spring bean that implementsRequestToViewNameTranslatorin your application context, Spring will detect and use it as appropriate. - The
RegularExpressionclass I am utilising is a helper class provided by my little commons library which simply wrapsjava.util.regex.Patternto make it easier to use. - The various configurable property values are obtained from an
application.propertiesfile (this contains properties that are internal to the application itself and are required but which might feasibly be overridden using another properties file taken from the target runtime environment. In accordance with the unit test, I therefore have the followingapplication.propertiesdefined:
org.apposite.view.ViewNameTranslator.uriPattern = ^/((?:admin/)?[a-zA-Z0-9-_]+)(?:\\.[a-zA-Z0-9]+)?(/|/([a-zA-Z0-9-_]+)(?:\\.[a-zA-Z0-9]+)?(/|/([a-zA-Z0-9-_]+)/?)?)?(?:\\.[a-zA-Z0-9]+)? org.apposite.view.ViewNameTranslator.entityIdentifierPattern = ^[0-9]+$ org.apposite.view.ViewNameTranslator.entityIndex = 1 org.apposite.view.ViewNameTranslator.actionIndex = 3 org.apposite.view.ViewNameTranslator.entityActionIndex = 5 org.apposite.view.ViewNameTranslator.defaultAction = list org.apposite.view.ViewNameTranslator.readAction = read
Dear Spring people: can you please add an optional=true attribute to the @Value annotation so that value injection from a properties file does not throw if left unconfigured? Then I could have defaults in the class and then only have to configure when I need to override. Thanks.
Next, let’s configure some Velocity basics and make some templates for the admin dashboard:
com.christophertownson.spring.velocity.RedirectViewResolver.order = 0 com.christophertownson.spring.velocity.VelocityViewResolver.order = 2 org.springframework.web.servlet.view.ContentNegotiatingViewResolver.order = 1
First, I set the chaining order of the various “view resolvers” that I will be using. The order is:
- Redirect view resolver comes first. We can always detect easily if it is a redirect view (because the view name starts with
redirect:. Moreover, these need to be handled differently. Therefore, we get them out of the way with at the start of the chain so there is no need for further processing. - Next we go to the
ContentNegotiatingViewResolverso that it can marshall the request and construct a sorted set of candidate view names (in order of file extension override orAcceptheader preference) on the basis of a media type mapping (e.g. given an appropriate media types mapping, a request resulting in the view name/homewith anAccept: text/jsonheader might result in the view name set/home.json, /home, giving subsequent view resolvers or configured default views the opportunity to satisfy the request using the clients preferred representation). - Finally, we come to my custom
VelocityViewResolver. This will look for an existing Velocity template corresponding to the view name. This means, for example, that, when used in conjunction with theContentNegotiatingViewResolver, we could configure aMappingJacksonJsonViewon the latter to serve a default JSON representation (serialized object graph) but, should we wish to customise that representation, we could simply drop in a/home.json.vmtemplate: theVelocityViewResolverwould consequently indicate to theContentNegotiatingViewResolverthat it could satisfy a request to represent the resource “/home” as JSON and would be delegated to in order to satisfy it accordingly in preference to theMappingJacksonJsonView. Because it works off of the same media types mapping as theContentNegotiatingViewResolver, the resultingVelocityViewproduced by theVelocityViewResolverwill dynamically be able to determine the correct content-type to use when streaming the content back to the client.
Next a little configuration that is unfortunately necessary only because Spring’s otherwise-extremely-handy @Value annotation has no “optional” or “default” attributes:
com.christophertownson.spring.velocity.DefaultVelocityInitialisationStrategy.order = 0 com.christophertownson.spring.velocity.VelocityToolsInitialisationStrategy.order = 1 com.christophertownson.spring.velocity.VelocityToolsWebInitialisationStrategy.order = 2 com.christophertownson.spring.velocity.VelocityConfiguration.defaultMediaType = application/xhtml+xml com.christophertownson.spring.velocity.VelocityConfiguration.defaultLayoutTemplate = /common/layouts/layout.vm com.christophertownson.spring.velocity.VelocityConfiguration.layoutContextKey = layout com.christophertownson.spring.velocity.VelocityConfiguration.prefix = /templates com.christophertownson.spring.velocity.VelocityConfiguration.screenContentKey = screen_content com.christophertownson.spring.velocity.VelocityConfiguration.suffix = .vm com.christophertownson.spring.velocity.VelocityConfiguration.toolboxUrl = /WEB-INF/toolbox.xml com.christophertownson.spring.velocity.SpringViewHelperRenderInterceptor.xhtml = true
This just sets the order of a bunch of “initialisation strategies” and properties for Velocity. As I say, whilst they need to be configur-able, there should be little need to ever change many of the above values because most are merely sensible defaults. If someone out there knows of a way to optionally inject values from properties files using Spring annotations, I would dearly love to hear from you!
And then a little more velocity configuration so that it is really working the way we want it to …
input.encoding = UTF-8 output.encoding = UTF-8 directive.foreach.maxloops = 1000 directive.set.null.allowed = true resource.loader = webapp, classpath classpath.resource.loader.description = Velocity Classpath Resource Loader classpath.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader webapp.resource.loader.description = Velocity Web Application Resource Loader webapp.resource.loader.class = org.apache.velocity.tools.view.WebappResourceLoader webapp.resource.loader.path = /WEB-INF webapp.resource.loader.cache = false webapp.resource.loader.modificationCheckInterval = 10 runtime.log.logsystem.class = org.apache.velocity.runtime.log.Log4JLogChute runtime.log.logsystem.log4j.logger = org.apache.velocity runtime.log.invalid.references = false velocimacro.library = /templates/common/macros/macros.vm,/org/springframework/web/servlet/view/velocity/spring.vm velocimacro.library.autoreload = true
I won’t detail what each of these options achieves except to point out that, on line 22, I specify the use of 2 macro libraries: one containing my own macros and the one provided by Spring (which is very useful for form binding).
Anyway, now that we’re mostly configured, let’s create an /admin/list.vm template to serve the default representation of the admin dashboard page:
<h2>Administration Dashboard</h2>
There we go. Nice and basic, I think you’ll agree (it just matches the simple assertion from the functional test that the heading will be “Administration Dashboard”).
I’m going to create my login form as a macro, because I thnk I can safely assume that, at some point, I will want to be able to put the form in multiple pages:
#macro(loginForm)
<form class="login" method="post" action="$linkTool.relative('/j_spring_security_check')">
<fieldset>
#label('j_username' 'Username') <input type="text" id="j_username" name="j_username" maxlength="255" />
#label('j_password' 'Password') <input type="password" id="j_password" name="j_password" maxlength="40" />
#submitInput('Login')
</fieldset>
</form>
#end
#macro(label $for $label)<label for="$for">$label</label>#end
#macro(submitInput $value)<input type="submit" value="$value" />#end
Consequently, all I need to do to create my login page is to create the following template:
#loginForm()
Let’s quickly create a default layout template …
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" version="-//W3C//DTD XHTML 1.1//EN">
<head>
<meta http-equiv="content-type" content="application/xhtml+xml;charset=utf-8" />
<title>Apposite</title>
</head>
<body>
<div id="header"><h1>Apposite</h1></div>
<div id="content">${screen_content}</div>
<div id="footer"><p>© Apposite 2011</p><!-- obligatory paranoid copyright notice --></div>
</body>
</html>
… and we’re almost there. We just need a little applicationContext.xml and web.xml jiggery-pokery and we’re pretty much done:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<context:property-placeholder location="classpath:application.properties,classpath:environment.properties" system-properties-mode="OVERRIDE" />
<context:component-scan base-package="org.apposite,com.christophertownson.spring.velocity" />
<security:http auto-config="true" disable-url-rewriting="true" path-type="regex">
<security:intercept-url pattern="/admin/?.*" access="${apposite.security.admin.role.name}" />
<security:form-login login-page="/users/login" default-target-url="/" authentication-failure-url="/users/login"/>
<security:logout logout-url="/users/logout"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="${apposite.security.root.user.name}" password="${apposite.security.root.user.password}" authorities="${apposite.security.admin.role.name}"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
<mvc:annotation-driven />
<mvc:default-servlet-handler />
<util:properties id="velocityProperties" location="classpath:velocity.properties" />
<bean id="velocityToolboxFactory" class="org.apache.velocity.tools.config.XmlFactoryConfiguration">
<constructor-arg type="boolean" value="false" />
</bean>
<util:map id="mediaTypes">
<entry key="json" value="application/json"/>
<entry key="xml" value="application/xml"/>
<entry key="rss" value="application/rss+xml" />
</util:map>
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="${org.springframework.web.servlet.view.ContentNegotiatingViewResolver.order}"/>
<property name="mediaTypes" ref="mediaTypes" />
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</constructor-arg>
<property name="contentType" value="application/xml;charset=UTF-8"/>
</bean>
</list>
</property>
</bean>
</beans>
It’s worth running through this file step-by-step …
- On line 14, I load the properties in order such that
application.propertieswill be overridden byenvironment.propertieswhich will in turn be overridden by system properties. - On line 16, I load annotated components from the namespaces
org.apposite(to get my Controllers) andcom.christophertownson.spring.velocity(to get my Velocity setup). - On lines 18–30, I configure a very basic Spring Security setup, using hard-coded users and passwords in the clear. This can be improved in future. Note, however, that I do externalise the user and role name configuration details into application or environment properties: this will make some things easier as things progress.
- On line 32, I declare annotation-driven MVC because this is a great addition to Spring that basically sets up everything you need to use annotated controllers (as I am).
- On line 34, I declare the default servlet handler. Another good new XML shorthand, this sets up handling of static resources by the container’s default servlet (as it says on the tin).
- On line 36, I instantiate a
java.util.Propertiesinstance with the bean IDvelocityPropertiesso that this is auto-wired into the default Velocity setup achieved via the component-scan of thecom.christophertownson.spring.velocitypackage. Similarly, on lines 38–40, I instantiate a Velocity toolbox factory, so that I can use Velocity tools using the new toolbox formats introduced with Velocity Tools 2. - On lines 42–6, I configure the media types mapping: this is a bi-directional map used by both the content-negotiating view resolver and the Velocity view to determine either (a) the file extension to use for a requested content-type or, inversely, (b) the content-type to use for a given file extension (falling back to a default content-type configured in application or environment properties when no content-type file extension is present). To add support for new formats, all we need to do is add it to the mapping and drop in corresponding templates for any resources for which you want a representation in that content-type to be available.
- Last, but by no means least, on lines 48–62, I instantiate the
ContentNegotiatingViewResolver, configuring it with the media types map and giving it theMappingJacksonJsonViewandXStreamMarshalleras default views (so that we can get a JSON or XML representation of any URI, if we so desire).
To get the whole thing working, so that the Spring application context is fired-up when the built WAR is deployed or started, we just need a standard web.xml that declares the relevant servlets and filters for Spring and Spring Security:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>apposite</display-name>
<description>A publishing application</description>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:org/apposite/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>httpMethod</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<servlet-name>apposite</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>httpMethod</filter-name>
<servlet-name>apposite</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>encoding</filter-name>
<servlet-name>apposite</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>apposite</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>apposite</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Note that the <servlet-name> is “apposite” and not “spring” (or something similarly generic) — it is a minor detail but this can result in some clearer log messages during startup, especially if you have a number of Spring webapps running in the same container. It also means that the DispatcherServlet will be looking for a WEB-INF/apposite-servlet.xml file: I could configure it to point to the main app context file but, instead, I usually opt to just stick an empty application context file there — historically, Spring recommend putting your controller and mapping declarations in this file but that becomes unnecessary when you are using annotated controllers. I am not a fan of having multiple application context XML files, whatever the supposed rationale for dividing them up: if you find you are having to do too much “pointy-bracket-configuration”, then simply hiding it across many files is not the answer!
In addition to the DispatcherServlet (which dispatches requests to the right controller), the DelegatingFilterProxy (which is here setting up the Spring Security filter), and the ContextLoaderListener (which is starting the main application context for access by the Spring Security filter and dispatcher servlet), I am also using the very useful HiddenHttpMethodFilter (to tunnel POST or GET requests from browser form submissions to more appropriate HTTP verbs) and the CharacterEncodingFilter (which does exactly what it says on the tin).
Now we are all ready to go and, given our end-to-end testing setup, we should be able to execute mvn test -PFunctionalTests from a command prompt at the project root and see the application started up, followed by the web driver test being run and passing.
As you may recall, I wanted to do a little more than just pass the test: I wanted to be able to get different representations of the same resource. Given the steps so far, it is possible to view GET /admin in default JSON or XML views generated by XStream or Jackson-Mapper simply by adding a .xml/.json file extension to the URI or (better) by issuing the request with an Accept: application/xml or Accept: application/json header. However, as there is no real “model” (object graph) associated with this simple page, you will begin to see some of the limitations of these default views … but, simply by dropping in a new template or two, we can selectively override the use of these default XML/JSON views:
$screen_content
First we create a totally “neutral” layout (because otherwise our default one would start trying to wrap our JSON or XML or whatever inside an HTML page layout). Now we can create, for example, an XML template for the admin dashboard page (leaving aside the question of the usefulness of such a representation of this resource for the time being):
#set($layout="/common/layouts/nolayout.vm")<?xml version="1.0" encoding="UTF-8"?> <admin><dashboard><title>Administration Dashboard</title></dashboard></admin>
Now, of course, that is not a stunningly useful example but I have no doubt that you can imagine a number of better use cases yourself. Within the “Apposite” application, for instance, I have a concept of a “CalendarEvent” within the domain model (which represents an “advertised, public event submitted by a user” as opposed to, say, an “application event”). I use this form of content-negotiation to deliver HTML, Atom, RDF, RSS, and ICS representations of events to the user from the same URI (with no additional programming required), whilst also being able to provide links for browsers within HTML pages by using the equivalent URI with the desired format’s file extension. If you compare this approach to the frankly painful process of adding RSS/Atom support using Spring’s own AbstractRssFeedView and Rome, I think you will be pleasantly surprised. Object graphs are object graphs and text is text. Turning the former into the latter should be a generic and abstract activity that requires no programming, only a syntactical description of the transformation (which is what a template is). Of course, there are more complex, binary formats that you may also wish to deliver for which this style of content negotiation is not appropriate … but, then, you are not tied to it. Because the Velocity View resolver lives at the end of the view resolver chain and will return null if it cannot locate an existing template, you can add your View implementations for more complex cases as default views on the ContentNegotiatingViewResolver and just use Velocity selectively to deliver customised representations of specific resources. To be honest, I have not yet had to satisfy any use cases that cannot be met by this simple method of content negotiation for text-based formats.