Content Negotiation using Spring MVC's ContentNegotiatingViewResolver

>> Monday, July 6, 2009

Hi all,

One of the new features in Spring 3.0 is REST support, REST clients can use the restTemplate class and server as part of the MVC framework. In chapter 18 of the 3.0.M3 documentation we can find a section describing content negotiation using the ContentNegotiatingViewResolver class. Lately I was invited to give a lecture about the new Spring 3.0 features and I prepared a detailed HOWTO example (if you want to skip the theory scroll down to HOTWO) of REST content negotiation which I decided to load to my blog, here it is


What is Content Negotiation?


Sometimes different HTTP clients would like to get different representations of a the same resource, for example the resource http://localhost/app/rest/users will list all of the users in a specific server - however one client would like to get the result as a XML document, another in a JSON format and the third as a human readable fancy HTML table - the process in which a client notifying the server about the preferred format (or formats) is named "content negotiation".

Content negotiation is part of the HTTP specification which defines two types of content negotiation - "server-driven negotiation" and "agent-driven negotiation". The scenario implemented by ContentNegotiatingViewResolver is server-driven negotiation. In abstract the specification defines server-driven negotiation as the following sequence:
  1. The agent (e.g., a browser) can include the following HTTP headers in a request: Accept, Accept-Charset, Accept-Encoding, Accept-Language, and User-Agent.
  2. Base on these headers the sever selects the best format (based on the server's internal logic) that match these criteria and send it to the client.



It is important to notice that the specification recommend using the above headers but it also states that: "However, an origin server is not limited to these dimensions and MAY vary the response based on any aspect of the request, including information outside the request-header fields or within extension header fields not defined by this specification." (here). I mention it since Spring's ContentNegotiatingViewResolver is doing just that.


The ContentNegotiatingViewResolver


The ContentNegotiatingViewResolver is a view resolver which instead of dispatching a view it delegates to another view resolver. The "secret" is in the way it decides to which resolver it should delegate to. The ContentNegotiatingViewResolver determines the client's preferred presentation form (a.k.a., the content or media type) based on the following algorithm:
  1. First it checks the request path extension (for example .../showUsers.xml vs. .../showUsers.pdf) against the ContentNegotiatingViewResolver defaults to determine the media type
  2. If there was no match on step 1, and if JAF is present on the environment it uses the FileTypeMap (javax.activation.FileTypeMap) to get the media type using the requested filename.
  3. As a last option it uses the Accept header

Once a media type was determined it asks each of the other view resolvers for a "candidate" view and checks to see if this view supports the requested media type, if so the request is delegated to the appropriate resolver (not to the candidate view, but to the resolver).

From steps 1 and 2 on above we can see that, as motioned before, Spring uses variant of request attributes to try and guess the media type and it doesn't stick only to the accept header.

Example


Environment
  • Spring 3.0.M3
  • Jetty 6.1.14

In my example I have a "user details" service that can be queried for users using username prefixes, I would like to render the output as XML, HTML and a "toString" format (a format I "invented" for the example in which I renders the user objects using the toString() method). My controller is as simple as it can be:

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class UsersController {

  private Map<String, UserDetails> users = new HashMap<String, UserDetails>();

  {
    UserDetails user = new UserDetails("eyal", new Date(), true);
    users.put(user.getName(), user);

    user = new UserDetails("john", new Date(), false);
    users.put(user.getName(), user);

    user = new UserDetails("emily", new Date(), true);
    users.put(user.getName(), user);

    user = new UserDetails("mark", new Date(), false);
    users.put(user.getName(), user);
  }

  @RequestMapping("/users/{namePrefix}")
  public String getUsers(@PathVariable("namePrefix") String prefix, Model model) {

    Collection<UserDetails> result = new LinkedList<UserDetails>();

    for (Entry<String, UserDetails> entry : users.entrySet()) {
      if (entry.getKey().startsWith(prefix)) {
        result.add(entry.getValue());
      }
    }
    model.addAttribute("users", result);
    return "usersListView";
  }
}


I map the controller using the @RequestMapping annotation to server any request which ends with any of the following patterns:/users/{namePrefix}, /users/{namePrefix}.*, and /users/{namePrefix}/. In my web.xml I map Spring's DispatcherServlet to the /rest/* url pattern so, for example, http://localhost:8080/<context>/rest/users/e, http://localhost:8080/<context>/rest/users/e.xml and http://localhost:8080/<context>/rest/users/.toString are all valid urls that will invoke my controller.


The controller returns the "usersListView" as the name of the next view to dispatch, technically I could do the content negotiation myself by adding the controller some logic that dispatches different views based on the request - for example if the file extension was '.xml' then I would try to dispatch the "usersListViewXML" and if it was '.htm' I would try to dispatch the "usersListViewHTM" view. But in a big application this can be very complicated to maintain - this is where the ContentNegotiatingViewResolver can help us by externalizing this logic. My controller will always return the "usersListView" as the next view but the actual view will be resolved using data from the actual HTTP request. Here is my application context:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:p="http://www.springframework.org/schema/p"
   xsi:schemaLocation="http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<context:component-scan base-package="**/eyallupu/**" />

<bean id="contentNegotiatingViewResolver" 
   class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
   p:order="#{T(org.springframework.core.Ordered).HIGHEST_PRECEDENCE}">
      <property name="mediaTypes">
         <map>
            <entry key="xml" value="application/xml" />
            <entry key="tostring" value="text/toString" />
            <entry key="html" value="text/html" />
         </map>
      </property>
</bean>

<bean id="marshallingViewResolver" class="com.jroller.blogs.eyallupu.rest.views.SingleViewViewResolver"
   p:order="#{contentNegotiatingViewResolver.order+1}">
      <constructor-arg>
         <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
            <constructor-arg>
               <bean class="org.springframework.oxm.xstream.XStreamMarshaller" />
            </constructor-arg>
         </bean>
      </constructor-arg>
</bean>

<bean id="toStringViewResolver" class="com.jroller.blogs.eyallupu.rest.views.SingleViewViewResolver"
  p:order="#{marshallingViewResolver.order+1}">
      <constructor-arg>
         <bean class="com.jroller.blogs.eyallupu.rest.views.ToStringView"
            p:contentType="text/toString" />
      </constructor-arg>
</bean>

<bean id="urlBasedViewRewsolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
   p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" p:order="#{toStringViewResolver.order+1}" />

</beans>


In the configuration above it is worth noticing the following

Registration of new media type

I set a value into the 'mediaTypes' property which maps file name extensions into media types. In traditional configuration I could skip it and let Spring use the defaults (as described in the beginning of this entry - filename extensions, JAF, and accept header) but since I wanted to illustrate how do I register a new media types (text/toString in this example) I set a value into this property. Using this configuration any request ends with '.toString' (case insensitive) will be mapped to the 'text/toString' media type.

The target View Resolvers

Other important components in this configuration is the view resolvers. I create three view resolvers - two of the SingleViewViewResolver type (more on that soon) and one is an instance of Spring's InternalResourceViewResolver. The ContentNegotiatingViewResolver will automatically locate these three resolvers and delegate to the first one that can serve a view with the required content type. The order of the resolvers is important and must follow the following rules:
  1. The ContentNegotiatingViewResolver itself must be with the highest precedence (I set that using the
    #{T(org.springframework.core.Ordered).HIGHEST_PRECEDENCE} expression)
  2. If more than one resolver can serve the same content type I must decide on their order to set my preferences

In my example I used Spring EL (SpEL) to explicitly set the order for the different resolvers, starting with the ContentNegotiationResolver as the first and continuing with the marshalling, toString and URL based view resolvers.


What is the SingleViewViewResolver?

I have two "generic views" - toString and XML, both are generated automatically using a view instance (ToStringView and MarshallingView) without a need for any concrete JSP or other template. I wanted to have an easy way to dispatch these views based on their bean names. But here is a catch - my controller returns the "usersListView" as the next view, but I can have only one bean named "usersListView" in my context, meaning that I cannot use the BeanNameViewResolver. One alternative - which was mentioned earlier - is to return a custom view name from the controller ("usersListViewXML" and "usersListViewToString") and to create two different beans (or actually to alias two generic beans with the concrete view name - for example a "toStringView" generic bean can be aliased as "usersListViewToString"). I choose a different way: I wrote a small view resolver which wraps one view and always returns it. By wrapping my views in a resolver I can register them into the ContentNegotiatingViewResolver and have them participating in the view selection process.

Putting It All Together


Let's assume that the following request arrives to my server: http://localhost:8080/spring30/rest/users/e.xml. What would happen?
  1. The DispatcherServlet will forward the request to my controller (UsersController)
  2. The users controller will find all of the users starting with 'e' put them in the model and select the "usersListView" as the next view
  3. The first resolver Spring MVC consults is ContentNegotiatingViewResolver (since it has the highest priority)
  4. The resolver figures out that the request media type is 'application/xml' and it starts to iterate and interview other resolvers (by their order)
  5. The first resolver interviewed is the instance of SingleViewViewResolver which wraps the MarshallingView, it returns that view
  6. The ContentNegotiationViewResolver checks to see if that view supports the 'application/xml' media type - it is
  7. The request is forward to the SingleViewViewResolver which will forward it to the MarshallingView
  8. The marshaling view creates the XML and returns it to the client

If the request would end with .html both instance of SingleViewViewResolver would fail to return a view which satisfies the need for a 'text/html' media type and the third view resolver would be invoked. This resolver dispatches the request to 'usersListView.jsp' which renders the response as an HTML page.

4 comments:

Anonymous August 14, 2010 at 10:42 PM  

Very Interesting!
Thank You!

Anonymous August 18, 2010 at 8:06 PM  

Good post and this enter helped me alot in my college assignement. Thanks you as your information.

paul November 22, 2010 at 9:31 PM  

great post. i wanna share also a spoon-feed tutorial on Spring MVC

http://www.adobocode.com/spring/a-spring-web-mvc-tutorial

and add step-by-step Hibernate JPA capabilities tutorial to it:

http://www.adobocode.com/spring/adding-crud-capability-to-spring-mvc

hope it will help people!

Vikash Kumar May 12, 2014 at 8:22 PM  

Great Article Thanks a lot. but I want ask you one question
How can create immutable class in java in a simple and effective example Please reply my question.

  © Blogger templates Sunset by Ourblogtemplates.com 2008

Back to TOP