Tag Archives: apposite

[repost ]Content Negotiation with Spring 3 MVC & Velocity

original:http://christophertownson.com/writing/38

One of my pet sub­jects is con­tent nego­ti­a­tion. There are many ben­e­fits to a REST­ful approach to web appli­ca­tion archi­tec­ture but few which fill me with such a curi­ous delight as the spec­ta­cle of see­ing loads of dif­fer­ent rep­re­sen­ta­tions of the same under­ly­ing resource being returned at the flick of an Accept header. That prob­a­bly makes me a very weird and geeky per­son but what the hell: con­tent nego­ti­a­tion is fun! … espe­cially when it is achieved with prac­ti­cally zero effort.

Any­how, some of the approaches I have seen to con­tent nego­ti­a­tion seem a lit­tle “over­weight”, fre­quently involv­ing sig­nif­i­cant amounts of cod­ing in order to add sup­port for new rep­re­sen­ta­tions. To be fair, they are intended to pro­vide über solu­tions to every con­ceiv­able sce­nario — which is great when you encounter one of those 1%-of-the-time sit­u­a­tions. Nor­mally, how­ever, you will sim­ply be return­ing a new text-based for­mat. Surely that should be a breeze, right? Well, yes and no.

One of the great improve­ments to Spring in ver­sion 3 is its sup­port for REST and con­tent nego­ti­a­tion in par­tic­u­lar. It also pro­vides almost-out-of-the-box solu­tions for pro­duc­ing JSON and XML rep­re­sen­ta­tions of a view model that might oth­er­wise be ren­dered as HTML (i.e. when we don’t ask for either JSON or XML specif­i­cally). But some­times they don’t quite give you the results you want and cus­tomi­sa­tion of the out­put is nigh-on impos­si­ble, so you have to go back to writ­ing some code. Which is a pain. But hold on a sec­ond … am I not already using a mech­a­nism for seri­al­iz­ing a Java object graph as text in order to pro­duce HTML pages? Indeed I am. In my case, in my hobby project which I call “Appo­site” (see my post on end-to-end BDD test­ing), I am using Apache Veloc­ity

In the post on BDD test­ing, I setup a sin­gle test case which asserted that a “admin­is­tra­tor” could log in to an “admin­is­tra­tion dash­board”. Let’s take up that case and try not just to make it pass but also to make the result­ing page view­able in a num­ber of dif­fer­ent for­mats (e.g. atom, rss, xml, json, plain text etc). Really, I should be writ­ing BDD tests specif­i­cally to cover each of these “view as for­mat” sce­nar­ios but, as they are not really part of the appli­ca­tion require­ments and I am only doing it for demon­stra­tion pur­poses, I am going to skip that for now.

Once again, let’s take this step-by-step and begin by set­ting up our basic Spring web appli­ca­tion. First off, we need to add the Spring depen­den­cies to the pom.xml, if not already present:

pom.xml
15
16
17
18
<properties>
    <spring.version>3.0.5.RELEASE</spring.version>
    <spring.security.version>${spring.version}</spring.security.version>
</properties>

First up, under the DRY prin­ci­ple, I declare the Spring ver­sion num­ber as a property.

pom.xml
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<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 frame­work is well mod­u­larised, so there are a num­ber of dif­fer­ent depen­den­cies required. It can become a lit­tle con­fus­ing when try­ing to deter­mine which Spring depen­den­cies one actu­ally requires but this is prefer­able, in my view (and, obvi­ously, the view of the peo­ple at Spring), to includ­ing a sin­gle mas­sive jar con­tain­ing tonnes of stuff you don’t need. The key mod­ules in the list above are:

spring-context
Con­tains the Spring appli­ca­tion con­text classes and anno­ta­tions to enable you actu­ally con­fig­ure and cre­ate a Spring context.
spring-core
For the pur­poses of our web appli­ca­tion, the most impor­tant aspect of the spring-core.jar is that it is the home forthe new, uni­fied con­ver­sion API intro­duced with Spring 3. This rep­re­sents a sig­nif­i­cant improve­ment over the pre­vi­ousPropertyEditor based sup­port for data binding.
spring-web
This is obvi­ously nec­es­sary for a web appli­ca­tion, con­tain­ing, as it does, most of the basic Servlet API inte­gra­tion code includ­ing the ContextLoaderListener and request map­ping annotations.
spring-webmvc
Con­tains a num­ber impor­tant sup­port classes and servlet API inte­gra­tions for cre­at­ing MVC web apps; notably theDispatcherServlet and the MvcNamespaceHandler (more on which below). Con­ven­tion­ally, this is also used to pro­vide view classes, such as Spring’s Veloc­ity and Freemarker imple­men­ta­tions. How­ever, I will be util­is­ing instead my own Veloc­ity inte­gra­tion library, because it is, of course, much bet­ter! (It is my spring-velocity library that makes the con­tent nego­ti­a­tion we are going to be using pos­si­ble so, whilst I won’t go into the imple­men­ta­tion in detail, I will note some key dif­fer­ences with the usual inte­gra­tion with Veloc­ity pro­vided by Spring out-of-the-box.)
spring-security-core
Spring Secu­rity will be used to pro­vide user authen­ti­ca­tion ser­vices since the test case I want to pass requires this.

Because I am using my own Spring-Velocity inte­gra­tion library, I also need to declare my maven repos­i­tory in the POM:

pom.xml
555
556
557
558
559
560
<repositories>
    <repository>
        <id>christophertownson-com-public</id>
        <url>http://christophertownson.com/mvn/</url>
    </repository>
</repositories>

And add the library as a dependency …

pom.xml
47
48
49
50
51
52
53
54
55
56
57
<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 exclud­ing commons-logging (which is picked up as a tran­si­tive depen­dency via Veloc­ity and Spring) here because I am using slf4j so nei­ther need nor want it.

Finally, I also want to use the XStreamMarshaller and MappingJacksonJsonView to pro­duce default XML and JSON rep­re­sen­ta­tions, respec­tively, so I need to declare run­time depen­den­cies on XStream and Jackson-Mapper:

pom.xml
162
163
164
165
166
167
168
169
170
171
172
173
<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 our­selves of the test scenario:

src/test/resources/bdd/org/apposite/security.stories
1
2
3
4
5
6
7
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 dash­board con­troller” to sat­isfy this test, so let’s begin with that:

src/test/java/org/apposite/controller/admin/DashboardAdminControllerTest.java
13
14
15
16
17
18
19
20
21
22
23
24
25
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 sim­ple test should be suf­fi­cient for now: all it asserts is that there is a “get dash­board” method which, for now, just returns an empty model. That should be easy enough to pass …

src/main/java/org/apposite/controller/admin/DashboardAdminController.java
11
12
13
14
15
16
17
@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 cre­ation of the first con­troller here, we are set­ting up a name­space and nam­ing con­ven­tion for “admin­is­tra­tion con­trollers” whereby they will live in the org.apposite.controller.admin pack­age and be named*AdminController.
  • The con­troller knows only about the model and noth­ing about view names (there are some unsat­is­fac­tory points to come where it will need to know about the lat­ter and I will have to com­pro­mise with the frame­work, but that is another story).
  • The unit test cov­ers only what the con­troller, as a class, knows about (i.e. the model). The con­fig­u­ra­tion (request map­ping, in this case) is pro­vided as meta­data on the class and implic­itly cov­ered by the end-to-end func­tional test.

We’re also going to need some kind of “login con­troller”. This is cross-cutting func­tion­al­ity but is per­formed by a “User”, so I’m going to cre­ate a UserController for this purpose:

/src/test/java/org/apposite/controller/UserControllerTest.java
13
14
15
16
17
18
19
20
21
22
23
24
25
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>()));
    }
}
src/main/java/org/apposite/controller/UserController.java
10
11
12
13
14
15
16
@Controller public class UserController extends AbstractController {
    @RequestMapping(value = "/users/login", method = RequestMethod.GET) public Map<String, Object> login() {
        return new HashMap<String, Object>();
    }
}

Because nei­ther con­troller is yet required to gen­er­ate any model data, they are both extremely simple.

Next, we need to pro­vide some way to map the request to a view tem­plate. For this pur­pose, Spring pro­vides theRequestToViewNameTranslator inter­face. I’m obvi­ously using TDD here (if you aren’t these days, what are you doing?!), so I begin with a test for my implementation:

src/test/java/org/apposite/view/ViewNameTranslatorTest.java
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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 prob­a­bly (and should be) think­ing “What the hell is going on in that setup method?” I will admit that I am jumping-the-gun a lit­tle here, but it is worth doing, I think: one of my non-functional require­ments for this appli­ca­tion is that all URIs are pre­dictable on the basis of a pat­tern descrip­tion (i.e. a reg­u­lar expres­sion). In other words, it is con­ven­tional. This can become restric­tive but, on the whole, I feel that the con­sis­tency it lends to the appli­ca­tion is desir­able from both a developer’s and a user’s per­spec­tive: “usabil­ity” is a phe­nom­e­non that can be described in terms of “intu­itive­ness” which, in turn, can be described as a form of pre-reflective pat­tern recog­ni­tion. Usabil­ity is a vital con­sid­er­a­tion because, tele­o­log­i­cally, it describes a ten­dency to facil­i­tate, rather than hin­der, intent and action (whether that is of a devel­oper extend­ing a code base or a user attempt­ing to com­plete some sce­nario). There­fore, all my URIs will take the fol­low­ing form, where each ele­ment is optional:

  1. A name­space (e.g. “admin”)
  2. An entity name
  3. An entity identifier
  4. An action name (ide­ally, we would be able to encap­su­late the con­cept of an “action” entirely within the use ofHTTP verbs but there are occa­sions where it is nec­es­sary to pro­vide URIs that include an action in order to eas­ily sup­port con­ven­tional human inter­ac­tion via a web browser).
  5. A file exten­sion (which can used to over­ride the content-type spec­i­fied by the Accept header where necessary).

My ViewNameTranslator imple­men­ta­tion will, con­se­quently, have some URI pat­terns defined with cap­ture group indexes set cor­re­spond­ingly so that it can parse out the rel­e­vant con­stituent parts of my URIs. Nonethe­less, 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.

src/main/java/org/apposite/view/ViewNameTranslator.java
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@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:

  1. It is anno­tated with @Component: it is a Spring bean. No bean ID is nec­es­sary. Sim­ply by hav­ing a Spring bean that imple­ments RequestToViewNameTranslator in your appli­ca­tion con­text, Spring will detect and use it as appropriate.
  2. The RegularExpression class I am util­is­ing is a helper class pro­vided by my lit­tle com­mons library which sim­ply wraps java.util.regex.Pattern to make it eas­ier to use.
  3. The var­i­ous con­fig­urable prop­erty val­ues are obtained from an application.properties file (this con­tains prop­er­ties that are inter­nal to the appli­ca­tion itself and are required but which might fea­si­bly be over­rid­den using another prop­er­ties file taken from the tar­get run­time envi­ron­ment. In accor­dance with the unit test, I there­fore have the fol­low­ing application.properties defined:
src/main/resources/application.properties
19
20
21
22
23
24
25
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 peo­ple: can you please add an optional=true attribute to the @Value anno­ta­tion so that value injec­tion from a prop­er­ties file does not throw if left uncon­fig­ured? Then I could have defaults in the class and then only have to con­fig­ure when I need to over­ride. Thanks.

Next, let’s con­fig­ure some Veloc­ity basics and make some tem­plates for the admin dashboard:

src/main/resources/application.properties
1
2
3
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 chain­ing order of the var­i­ous “view resolvers” that I will be using. The order is:

  1. Redi­rect view resolver comes first. We can always detect eas­ily if it is a redi­rect view (because the view name starts with redirect:. More­over, these need to be han­dled dif­fer­ently. There­fore, we get them out of the way with at the start of the chain so there is no need for fur­ther processing.
  2. Next we go to the ContentNegotiatingViewResolver so that it can mar­shall the request and con­struct a sorted set of can­di­date view names (in order of file exten­sion over­ride or Accept header pref­er­ence) on the basis of a media type map­ping (e.g. given an appro­pri­ate media types map­ping, a request result­ing in the view name /home with anAccept: text/json header might result in the view name set /home.json, /home, giv­ing sub­se­quent view resolvers or con­fig­ured default views the oppor­tu­nity to sat­isfy the request using the clients pre­ferred representation).
  3. Finally, we come to my cus­tom VelocityViewResolver. This will look for an exist­ing Veloc­ity tem­plate cor­re­spond­ing to the view name. This means, for exam­ple, that, when used in con­junc­tion with theContentNegotiatingViewResolver, we could con­fig­ure a MappingJacksonJsonView on the lat­ter to serve a defaultJSON rep­re­sen­ta­tion (seri­al­ized object graph) but, should we wish to cus­tomise that rep­re­sen­ta­tion, we could sim­ply drop in a /home.json.vm tem­plate: the VelocityViewResolver would con­se­quently indi­cate to theContentNegotiatingViewResolver that it could sat­isfy a request to rep­re­sent the resource “/home” as JSON and would be del­e­gated to in order to sat­isfy it accord­ingly in pref­er­ence to the MappingJacksonJsonView. Because it works off of the same media types map­ping as the ContentNegotiatingViewResolver, the result­ing VelocityViewpro­duced by the VelocityViewResolver will dynam­i­cally be able to deter­mine the cor­rect content-type to use when stream­ing the con­tent back to the client.

Next a lit­tle con­fig­u­ra­tion that is unfor­tu­nately nec­es­sary only because Spring’s otherwise-extremely-handy @Value anno­ta­tion has no “optional” or “default” attributes:

src/main/resources/application.properties
5
6
7
8
9
10
11
12
13
14
15
16
17
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 “ini­tial­i­sa­tion strate­gies” and prop­er­ties for Veloc­ity. As I say, whilst they need to be configur-able, there should be lit­tle need to ever change many of the above val­ues because most are merely sen­si­ble defaults. If some­one out there knows of a way to option­ally inject val­ues from prop­er­ties files using Spring anno­ta­tions, I would dearly love to hear from you!

And then a lit­tle more veloc­ity con­fig­u­ra­tion so that it is really work­ing the way we want it to …

src/main/resources/velocity.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 spec­ify the use of 2 macro libraries: one con­tain­ing my own macros and the one pro­vided by Spring (which is very use­ful for form binding).

Any­way, now that we’re mostly con­fig­ured, let’s cre­ate an /admin/list.vm tem­plate to serve the default rep­re­sen­ta­tion of the admin dash­board page:

src/main/webapp/WEB-INF/templates/admin/list.vm
1
<h2>Administration Dashboard</h2>

There we go. Nice and basic, I think you’ll agree (it just matches the sim­ple asser­tion from the func­tional test that the head­ing will be “Admin­is­tra­tion Dashboard”).

I’m going to cre­ate 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 mul­ti­ple pages:

src/main/webapp/WEB-INF/templates/commons/macros/macros.vm
1
2
3
4
5
6
7
8
9
10
11
12
13
#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

Con­se­quently, all I need to do to cre­ate my login page is to cre­ate the fol­low­ing template:

src/main/webapp/WEB-INF/templates/users/login.vm
1
#loginForm()

Let’s quickly cre­ate a default lay­out template …

src/main/webapp/WEB-INF/templates/common/layouts/layout.vm
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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>&copy; Apposite 2011</p><!-- obligatory paranoid copyright notice --></div>
    </body>
</html>

… and we’re almost there. We just need a lit­tle applicationContext.xml and web.xml jiggery-pokery and we’re pretty much done:

src/main/resources/org/apposite/applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?xml version="1.0" encoding="UTF-8"?>
    <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 run­ning through this file step-by-step …

  1. On line 14, I load the prop­er­ties in order such that application.properties will be over­rid­den byenvironment.properties which will in turn be over­rid­den by sys­tem properties.
  2. On line 16, I load anno­tated com­po­nents from the name­spaces org.apposite (to get my Con­trollers) andcom.christophertownson.spring.velocity (to get my Veloc­ity setup).
  3. On lines 18–30, I con­fig­ure a very basic Spring Secu­rity setup, using hard-coded users and pass­words in the clear. This can be improved in future. Note, how­ever, that I do exter­nalise the user and role name con­fig­u­ra­tion details into appli­ca­tion or envi­ron­ment prop­er­ties: this will make some things eas­ier as things progress.
  4. On line 32, I declare annotation-driven MVC because this is a great addi­tion to Spring that basi­cally sets up every­thing you need to use anno­tated con­trollers (as I am).
  5. On line 34, I declare the default servlet han­dler. Another good new XML short­hand, this sets up han­dling of sta­tic resources by the container’s default servlet (as it says on the tin).
  6. On line 36, I instan­ti­ate a java.util.Properties instance with the bean ID velocityProperties so that this is auto-wired into the default Veloc­ity setup achieved via the component-scan of thecom.christophertownson.spring.velocity pack­age. Sim­i­larly, on lines 38–40, I instan­ti­ate a Veloc­ity tool­box fac­tory, so that I can use Veloc­ity tools using the new tool­box for­mats intro­duced with Veloc­ity Tools 2.
  7. On lines 42–6, I con­fig­ure the media types map­ping: this is a bi-directional map used by both the content-negotiating view resolver and the Veloc­ity view to deter­mine either (a) the file exten­sion to use for a requested content-type or, inversely, (b) the content-type to use for a given file exten­sion (falling back to a default content-type con­fig­ured in appli­ca­tion or envi­ron­ment prop­er­ties when no content-type file exten­sion is present). To add sup­port for new for­mats, all we need to do is add it to the map­ping and drop in cor­re­spond­ing tem­plates for any resources for which you want a rep­re­sen­ta­tion in that content-type to be available.
  8. Last, but by no means least, on lines 48–62, I instan­ti­ate the ContentNegotiatingViewResolver, con­fig­ur­ing it with the media types map and giv­ing it the MappingJacksonJsonView and XStreamMarshaller as default views (so that we can get a JSON or XML rep­re­sen­ta­tion of any URI, if we so desire).

To get the whole thing work­ing, so that the Spring appli­ca­tion con­text is fired-up when the built WAR is deployed or started, we just need a stan­dard web.xml that declares the rel­e­vant servlets and fil­ters for Spring and Spring Security:

src/main/webapp/WEB-INF/web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    <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 “appo­site” and not “spring” (or some­thing sim­i­larly generic) — it is a minor detail but this can result in some clearer log mes­sages dur­ing startup, espe­cially if you have a num­ber of Spring webapps run­ning in the same con­tainer. It also means that the DispatcherServlet will be look­ing for a WEB-INF/apposite-servlet.xml file: I could con­fig­ure it to point to the main app con­text file but, instead, I usu­ally opt to just stick an empty appli­ca­tion con­text file there — his­tor­i­cally, Spring rec­om­mend putting your con­troller and map­ping dec­la­ra­tions in this file but that becomes unnec­es­sary when you are using anno­tated con­trollers. I am not a fan of hav­ing mul­ti­ple appli­ca­tion con­text XML files, what­ever the sup­posed ratio­nale for divid­ing them up: if you find you are hav­ing to do too much “pointy-bracket-configuration”, then sim­ply hid­ing it across many files is not the answer!

In addi­tion to the DispatcherServlet (which dis­patches requests to the right con­troller), the DelegatingFilterProxy(which is here set­ting up the Spring Secu­rity fil­ter), and the ContextLoaderListener (which is start­ing the main appli­ca­tion con­text for access by the Spring Secu­rity fil­ter and dis­patcher servlet), I am also using the very use­fulHiddenHttpMethodFilter (to tun­nel POST or GET requests from browser form sub­mis­sions to more appro­pri­ate HTTPverbs) 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 test­ing setup, we should be able to exe­cute mvn test -PFunctionalTests from a com­mand prompt at the project root and see the appli­ca­tion started up, fol­lowed by the web dri­ver test being run and passing.

As you may recall, I wanted to do a lit­tle more than just pass the test: I wanted to be able to get dif­fer­ent rep­re­sen­ta­tions of the same resource. Given the steps so far, it is pos­si­ble to view GET /admin in default JSON or XML views gen­er­ated by XStream or Jackson-Mapper sim­ply by adding a .xml/.json file exten­sion to the URI or (bet­ter) by issu­ing the request with an Accept: application/xml or Accept: application/json header. How­ever, as there is no real “model” (object graph) asso­ci­ated with this sim­ple page, you will begin to see some of the lim­i­ta­tions of these default views … but, sim­ply by drop­ping in a new tem­plate or two, we can selec­tively over­ride the use of these default XML/JSON views:

src/main/webapp/WEB-INF/templates/common/layouts/nolayout.vm
1
$screen_content

First we cre­ate a totally “neu­tral” lay­out (because oth­er­wise our default one would start try­ing to wrap our JSON or XML or what­ever inside an HTML page lay­out). Now we can cre­ate, for exam­ple, an XML tem­plate for the admin dash­board page (leav­ing aside the ques­tion of the use­ful­ness of such a rep­re­sen­ta­tion of this resource for the time being):

src/main/webapp/WEB-INF/templates/admin/list.xml.vm
1
2
#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 stun­ningly use­ful exam­ple but I have no doubt that you can imag­ine a num­ber of bet­ter use cases your­self. Within the “Appo­site” appli­ca­tion, for instance, I have a con­cept of a “Cal­en­darEvent” within the domain model (which rep­re­sents an “adver­tised, pub­lic event sub­mit­ted by a user” as opposed to, say, an “appli­ca­tion event”). I use this form of content-negotiation to deliver HTML, Atom, RDF, RSS, and ICS rep­re­sen­ta­tions of events to the user from the same URI (with no addi­tional pro­gram­ming required), whilst also being able to pro­vide links for browsers withinHTML pages by using the equiv­a­lent URI with the desired format’s file exten­sion. If you com­pare this approach to the frankly painful process of adding RSS/Atom sup­port using Spring’s own AbstractRssFeedView and Rome, I think you will be pleas­antly sur­prised. Object graphs are object graphs and text is text. Turn­ing the for­mer into the lat­ter should be a generic and abstract activ­ity that requires no pro­gram­ming, only a syn­tac­ti­cal descrip­tion of the trans­for­ma­tion (which is what a tem­plate is). Of course, there are more com­plex, binary for­mats that you may also wish to deliver for which this style of con­tent nego­ti­a­tion is not appro­pri­ate … but, then, you are not tied to it. Because the Veloc­ity View resolver lives at the end of the view resolver chain and will return null if it can­not locate an exist­ing tem­plate, you can add your Viewimple­men­ta­tions for more com­plex cases as default views on the ContentNegotiatingViewResolver and just use Veloc­ity selec­tively to deliver cus­tomised rep­re­sen­ta­tions of spe­cific resources. To be hon­est, I have not yet had to sat­isfy any use cases that can­not be met by this sim­ple method of con­tent nego­ti­a­tion for text-based formats.