On September 17, 2011 christopher wrote: Handling Multiple File Uploads With Uploadify

More JavaScript fun this week, dur­ing which I have been play­ing around with Upload­ify, one of a slew of ready-made JavaScript + Flash solu­tions for han­dling mul­ti­ple file uploads in the browser. I have cho­sen to look at Upload­ify over the other alter­na­tives due to the fact that I find it per­son­ally to be the most sim­ple to use. Also, it is pro­vided as an exten­sion to jQuery, which is prob­a­bly my favourite JavaScript library. Last, but not least, it works … which is a qual­ity never to be under­es­ti­mated in software!

As usual, I am going to be demon­strat­ing its use in con­junc­tion with a Spring-Velocity web app. Rather than go through the whole process of set­ting up such an app again, I would refer you to my post on ActiveMQ, JMS & Ajax. The struc­ture of the project I talk about here is iden­ti­cal to that, but with fewer depen­den­cies, and, of course, you can down­load the code if you want to take a closer look. So let’s begin …

Let’s begin by imag­in­ing what our use case here might look like. For the sake of argu­ment, I am going to say that what we want is this:

  • The user needs to be able to upload one or more files.
  • The names of these files must match a nam­ing con­ven­tion from which the appli­ca­tion infers some kind of seman­tic value (which might be used to process them in dif­fer­ent ways, for example).
  • When the user uploads valid file(s), they should see a list of the files uploaded with the seman­tic infor­ma­tion we have parsed from them via their name(s).
  • When the user uploads invalid file(s), they should see infor­ma­tion about this error.

In my domain model, there­fore, I have 2 classes:

public enum UploadFileType {

    BAR(Pattern.compile("(?i)^[a-z0-9]+-bar\\.[a-z]+$")),
    BAZ(Pattern.compile("(?i)^[a-z0-9]+-baz\\.[a-z]+$")),
    FOO(Pattern.compile("(?i)^[a-z0-9]+-foo\\.[a-z]+$"));

    public static UploadFileType forName(String name) {
        for (UploadFileType type : values()) {
            if (type.p.matcher(name).matches()) return type;
        }
        throw new IllegalArgumentException(name + " does not match any of the patterns in the naming convention");
    }

    private Pattern p;

    private UploadFileType(Pattern p) {
        this.p = p;
    }

}

Because the logic required to parse my nam­ing con­ven­tion is sim­ple, I do not really need any kind of com­plex fac­tory … all I need is this sim­ple enum. You give it the file name in the forName(String) method and it returns the type. If no type is found, we throw an IllegalArgumentException which we will use later to man­age error flow. After this, I have a sim­ple Java bean to rep­re­sent the model for an UploadFile:

public class UploadFile {

    private final File file;

    private final String name;

    private final UploadFileType type;

    public UploadFile(File file, String name, UploadFileType type) {
        this.file = file;
        this.name = name;
        this.type = type;
    }

    @Override public boolean equals(Object obj) {
        return reflectionEquals(this, obj);
    }

    public File getFile() {
        return file;
    }

    public String getName() {
        return name;
    }

    public UploadFileType getType() {
        return type;
    }

    @Override public int hashCode() {
        return reflectionHashCode(this);
    }

    @Override public String toString() {
        return reflectionToString(this);
    }

}

Now, let’s assume I have a web page with a com­pletely stan­dard HTML form with a file input:

                <form id="upload_file" action="/upload" method="post" enctype="multipart/form-data">
                    <fieldset>
                        <legend>Choose the files to upload</legend>
                        <label for="file">File</label> <input id="file" name="file" type="file" /> <input type="submit" value="Upload" />
                    </fieldset>
                </form>

And, on the server-side, using Spring’s mul­ti­part form sup­port, I have a FileController that looks like this:

@Controller public class FileController {

    @RequestMapping(value = "/", method = RequestMethod.GET) public String getFileUploadViewName() {
        return "/fileupload";
    }

    @RequestMapping(value = "/upload", method = RequestMethod.POST) public UploadFile upload(@RequestParam("file") MultipartFile mf) throws IOException {
        String name = getName(mf.getOriginalFilename());
        File f = File.createTempFile(name, "." + getExtension(name));
        mf.transferTo(f);
        return new UploadFile(f, name, UploadFileType.forName(name));
    }

    @ExceptionHandler(IllegalArgumentException.class) @ResponseBody public String handleIllegalArgumentException(IllegalArgumentException e) {
        return "{\"error\":{\"message\":\"" + e.getMessage() + "\"}}";
    }

}

For the sake of clar­ity, here is what each of the three con­troller meth­ods do:

  1. getFileUploadViewName() sim­ply returns the Spring view name required to return the HTML file upload form. I have hard-coded this name for now but, in real life, you should prob­a­bly inject this as configuration.
  2. upload(MultipartFile) is the meat of the con­troller class: Spring will bind the mul­ti­part file from the file input of the sub­mit­ted form which has name="file". The only pro­cess­ing we are doing with this file for now is cre­at­ing a temp file from it and deter­min­ing its “type” by using the UploadFileType.forName(String) method. This will either return the type (in which case we have suc­cess­fully an UploadFile which we return as the model object) or it will throw the IllegalArgumentException in which case, our Spring error han­dler method is called …
  3. The handleIllegalArgumentException method is a Spring error han­dler method (indi­cated by the @ExceptionHandler anno­ta­tion. In such an event, all we do is return a hand-crafted JSON string as the response body with the excep­tion mes­sage in it … which is a bit lazy, I admit — but it will do for now.

With this in place, we have an end-to-end con­ven­tion HTML single-file upload mech­a­nism in place. So now it is time to dec­o­rate it with a lit­tle JavaScript magic so that the whole thing becomes a bit more usable and so that it becomes pos­si­ble for the user to upload mul­ti­ple files.

First, to pro­vide the user with the nec­es­sary feed­back in the page from the file uploads, I am going to add a few place­holder sec­tions to my HTML page within which I will dis­play data from the parsed JSON responses pro­vided by the controller …

        <section id="file_queue">
            <hgroup>
                <h1>File Upload Queue</h1>
                <div id="queue">
                </div>
            </hgroup>
        </section>
        <section id="files">
            <hgroup>
                <h1>Uploaded Files</h1>
                <ol>
                </ol>
            </hgroup>
        </section>
        <section id="errors">
            <hgroup>
                <h1>Errors</h1>
                <ol>
                </ol>
            </hgroup>
        </section>

We add, first, a sec­tion that will be used by Upload­ify to dis­play the queue of files as they are being uploaded with the ID “queue”. This is a built-in fea­ture of Upload­ify, so we don’t need to do any­thing spe­cial to get this queue dis­played, other than let Upload­ify know the ID of our “queue div” by set­ting the queueID prop­erty. After that, I have placed the 2 sec­tions that we will be writ­ing the info from the parsed JSON responses from the con­troller into — 1 for suc­cess­ful file uploads and 1 for errors.

Now I am just about ready to sprin­kle a lit­tle JavaScript on the page … But, stop! Before I do this there is ONE VERY IMPORTANT GOTCHA to deal with!

Now, due to the nature of the genius minds that work at Adobe, it is appar­ently impos­si­ble to set HTTP head­ers in requests made by Flash (I know, I know — what kind of non­sense is that?!) I am not a Flash expert, so I don’t know the ins-and-outs of this. How­ever, since Upload­ify (and pretty much any JavaScript-based mul­ti­file uploader) uses swfobject.js and Flash, this means that an Accept header can­not be set to request JSON responses. By default, as far as I can work out, Flash will always send an Accept header request­ing text/*. If you are using Spring’s ContentNegotiatingViewResolver (which I am in this exam­ple), you will need to work around this prob­lem. There are 3 options: the first is to use a .json file exten­sion on the request URI to indi­cate the required media type for the response this way. The sec­ond option is to use a request para­me­ter to achieve the same thing. The final way is to fil­ter the request and over­ride the Accept header for Upload­ify requests.

In real life, I would prob­a­bly just send Upload­ify requests to /upload.json and then the ContentNegotiatingViewResolver would know to send a JSON response. How­ever, for what­ever rea­son, you might not want or be able to change the URI in this way, so I will demon­strate the Old Skool servlet fil­ter approach (you might think you could also use a Spring HandlerInterceptor but that leads to call-order prob­lems with the ContentNegotiatingViewResolver — using a clas­sic fil­ter will ensure that the request is inter­cepted and wrapped before it even reaches Spring:

public class UploadifyFilter implements Filter {

    @SuppressWarnings({ "deprecation", "rawtypes" }) private static class UploadifyRequest implements HttpServletRequest {

        private final HttpServletRequest del;

        UploadifyRequest(HttpServletRequest delegate) {
            del = delegate;
        }

        @Override public String getHeader(String name) {
            return "Accept".equals(name) ? "application/json" : del.getHeader(name);
        }

        /* ... all other delegate methods not shown for brevity ... */

    }

    @Override public void init(FilterConfig filterConfig) throws ServletException {}

    @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        if (req instanceof HttpServletRequest) chain.doFilter(new UploadifyRequest((HttpServletRequest) req), resp);
        else chain.doFilter(req, resp);
    }

    @Override public void destroy() {}

}

I then sim­ply wire this up in my web.xml to fil­ter requests sent to the /upload URI:

    <filter>
        <filter-name>uploadify</filter-name>
        <filter-class>com.christophertownson.foo.UploadifyFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>uploadify</filter-name>
        <url-pattern>/upload</url-pattern>
    </filter-mapping>

And now I am ready to receive Accept: text/* requests from the “Shock­wave Flash” user-agent and ensure that my app under­stands what content-type it should return. So let’s put in some JavaScript to make it all happen …

function init() {
    $('#file').uploadify({
        'script'         : '/upload', // URI to send the files to (could specify '/upload.json' here rather than using filter)
        'method'         : 'post', // request method to use
        'uploader'       : '/resources/uploadify.swf', // path to key uploadify files
        'expressInstall' : '/resources/expressInstall.swf', // path to key uploadify files
        'cancelImg'      : '/resources/cancel.png', // path to key uploadify files
        'auto'           : true, // automatically start uploading files (don't wait for user to push another button)
        'multi'          : true, // allow upload of multiple files
        'queueSizeLimit' : 10, // the number of files that can be placed into the queue at any 1 time
        'queueID'        : 'queue', // div ID to display the file upload queue in
        'fileDataName'   : 'file', // the request param name for the multipart file
        'fileExt'        : '*.txt;*.jpg;*.gif;*.png', // filter to use in the select files dialog
        'fileDesc'       : 'Upload Files (.txt, .jpg, .gif, .png)', // we MUST provide a description when we specify fileExt otherwise filter will NOT be applied
        // process JSON response to single file upload
        'onComplete'     : function(event, id, file, resp, data) {
            var obj = jQuery.parseJSON(resp);
            if (obj.error) {
                $('#errors ol').append('<li>' + obj.error.message + '</li>');
            } else if (obj.uploadFile) {
                $('#files ol').append('<li>' + obj.uploadFile.name + ' (Type: ' + obj.uploadFile.type +')</li>');
            }
        },
        // process HTTP 5xx errors etc
        'onError'        : function (event, id, file, err) {
            $('#errors ol').append('<li>' + err.type + ' Error: ' + err.info + '</li>')
        }
    });

    $('#upload_file').submit(function() {
        $('#file').uploadifyUpload();
        return false;
    });
}

$(document).ready(init);

It should be fairly evi­dent what this sim­ple JavaScript achieves but I will run through it briefly:

  1. It begins by con­fig­ur­ing some basic Upload­ify prop­er­ties includ­ing, per­haps most impor­tant for my pur­poses here 'multi' : true as this is what per­mits the upload of mul­ti­ple files.
  2. After that, we pro­vide an event call­back han­dler func­tion for the onComplete event — this event sig­nals com­ple­tion of the upload of an indi­vid­ual file within the queue and pro­vides access to the response from the server. In our case, this con­sists of a JSON string which we dese­ri­al­ize and append to the list of upload files or errors, as nec­es­sary. I also hook into the onError event to pro­vide sim­i­lar error han­dling for Upload­ify errors (rather than the log­i­cal excep­tions thrown by our con­troller that come back as JSON) … these are com­monly things like HTTP 5xx errors and so forth.
  3. Finally, I attach a lis­tener to the onSubmit event of the upload file form to pre­vent nor­mal sub­mis­sion and, instead, to get Upload­ify to han­dle the input.

There are many con­fig­u­ra­tion options and pos­si­bil­i­ties for event han­dling within the API. I would rec­om­mend read­ing the won­der­fully con­cise Upload­ify doc­u­men­ta­tion and it really is an excep­tion­ally easy piece of kit to use, espe­cially com­pared with the pain I recall try­ing to get some of the early ver­sions of the YUI uploader to work nicely a few years back … although the YUI3 Uploader looks a great improve­ment on pre­vi­ous ver­sions and is widely used and would also be well-worth considering.

On May 29, 2011 christopher wrote: Content Negotiation with Spring 3 MVC & Velocity

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:

    <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.

        <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 for the 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­ous PropertyEditor 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 the DispatcherServlet 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:

    <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 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:

        <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:

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:

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 …

@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:

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 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 the RequestToViewNameTranslator 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:

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 of HTTP 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.

@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:
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:

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 an Accept: 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 the ContentNegotiatingViewResolver, we could con­fig­ure a MappingJacksonJsonView on the lat­ter to serve a default JSON 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 the ContentNegotiatingViewResolver 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 VelocityView pro­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:

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 …

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:

<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:

#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:

#loginForm()

Let’s quickly cre­ate a default lay­out 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>&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:

<?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 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 by environment.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) and com.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 the com.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:

<?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 “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­ful HiddenHttpMethodFilter (to tun­nel POST or GET requests from browser form sub­mis­sions to more appro­pri­ate 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 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:

$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):

#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 within HTML 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 View imple­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.