Blogs

SpringSource Blog

Adding an Atom view to an application using Spring's REST support

Alef Arendsen

In Spring 3.0, Spring MVC will be augmented with REST support. This post describes how to use the REST support to implement an AtomView on top of a simple sample application. Follow this step-by-step process to see how easy it is to implement an AtomView on top of a simple application with the new REST support in Spring MVC.

Step 1: Download the application skeleton

Attached to this blog entry, near the bottom, you will find a simple download that holds a skeleton for a web application. Inside, you will find all Spring 3.0 binaries needed for this application, plus a few extras needed for the Atom functionality. The Spring binaries are based on a nightly build and might be replaced with the final builds once Spring 3.0 has gone final.

Next, load up the project in Eclipse, using the 'Import > Import Existing Projects into Workspace' wizard (from the File menu). The application is a simple Eclipse Dynamic Web Project with all the infrastructure for Spring MVC setup. So if you are familiar to Spring MVC, this shouldn't be too big of a deal.

Step 2: Review the setup of the application

In /WEB-INF/web.xml you will find the Spring MVC DispatcherServlet being defined. It loads up an application context from the /WEB-INF/rest-servlet.xml file. This file in its turn, contains a component scanner that scan for @Components (also @Controllers) in the com.springsource.samples.rest package.

Next, in the com.springsource.samples.rest package, you will find a ContentController with two controller methods.

@Controller
public class ContentController {

	private List<SampleContent> contentList = new ArrayList<SampleContent>();

	@RequestMapping(value="/content.*", method=RequestMethod.GET)
	public ModelAndView getContent() {
		ModelAndView mav = new ModelAndView();
		mav.setViewName("content");
		mav.addObject("sampleContentList", contentList);
		return mav;
	}

	@RequestMapping(value="/content.html", method=RequestMethod.POST)
	public String addContent() {
		contentList.add(SampleContent.generateContent("Alef Arendsen", new Date()));
		return "redirect:content.html";
	}
}

The first handler returns a list of SampleContent items. The second handler adds a new SampleContent item, by using the SampleContent.generateContent() method. The first handler reacts to GET requests, the second handler reacts to POST requests. The methods themselves have been annotated with @RequestMapping annotations to do this.

In the rest-servlet.xml application context file, in addition to the component scanner, you will also a ViewResolver, in this case an InternalResourceViewResolver. It will take care of the translation between the view name ('content' in the case of the getContent() handler) and the JSP page.

After having deployed this to for example Tomcat, you should be able to go to http://localhost:8080/spring-rest/rest. Thiswill redirect you to /rest/content, a URL that is picked up by the handler.

Step 3: Implementing the AtomView

To implement the AtomView, we will use the Rome project, available from https://rome.dev.java.net/. In the original set up of the application, view names are translated by the view resolver to instances of InternalResourceView, which in this case will render JSPs. We are going to create our own dedicated instance of the View interface, rendering Atom feeds instead.

Create a class called SampleContentAtomView in the com.springsource.samples.rest package and paste in the following code. The code uses the Atom support classes from Spring MVC and the document object model for Atom feeds from the Rome project.

public class SampleContentAtomView extends AbstractAtomFeedView {

	@Override
	protected void buildFeedMetadata(Map<String, Object> model, Feed feed, HttpServletRequest request) {
		feed.setId("tag:springsource.com");
		feed.setTitle("Sample Content");
		@SuppressWarnings("unchecked")
		List<SampleContent> contentList = (List<SampleContent>)model.get("sampleContentList");
		for (SampleContent content : contentList) {
			Date date = content.getPublicationDate();
			if (feed.getUpdated() == null || date.compareTo(feed.getUpdated()) > 0) {
				feed.setUpdated(date);
			}
		}
	}

	@Override
	protected List<Entry> buildFeedEntries(Map<String, Object> model,
			HttpServletRequest request, HttpServletResponse response) throws Exception {

		@SuppressWarnings("unchecked")
		List<SampleContent> contentList = (List<SampleContent>)model.get("sampleContentList");
		List<Entry> entries = new ArrayList<Entry>(contentList.size());

		for (SampleContent content : contentList) {
			Entry entry = new Entry();
			String date = String.format("%1$tY-%1$tm-%1$td", content.getPublicationDate());
			// see http://diveintomark.org/archives/2004/05/28/howto-atom-id#other
			 entry.setId(String.format("tag:springsource.com,%s:%d", date, content.getId()));
			entry.setTitle(String.format("On %s, %s wrote", date, content.getAuthor()));
			entry.setUpdated(content.getPublicationDate());

			Content summary = new Content();
			summary.setValue(content.getText());
			entry.setSummary(summary);

			entries.add(entry);
		}

		return entries;

	}
}

Step 4: Setting up content negotiation

The skeleton web application already provided the HTML view and now we have also implemented the view generating an Atom feed. The last thing we need to do is make sure requests for the Atom feed actually will render using the SampleContentAtomView and the JSP will be rendered when an HTML view is requested.

In a perfect world, a client would ask for a certain representation it prefers using the Accept HTTP header. The Accept HTTP header (as defined by the HTTP specification) can take one or more media types and should be sent by the browser (or any HTTP client for that matter) to indicate what kind of representation it prefers. An appropriate media type for an Atom feed for example would be 'application/atom+xml', while HTML views might just send 'text/html' or 'text/xhtml' as the Accept header. There is a slight problem with this however. Browsers, typically have a fixed set of media types they send along as the Accept HTTP header and there's no way (aside from using JavaScript) to modify the Accept header sent along by the browser. That's why the file extension is a good alternative to indicate to a server what representation you want.

Spring 3.0 features a ContentNegotiatingViewResolver that can work with both the extension as well as the Accept header. After it's figured out an appropriate media type, it delegates to a set of other view resolvers to do this for us. The following needs to be pasted into rest-servlet.xml to get this to work. As you can see, the ContentNegotiatingViewResolver will delegate to a a BeanNameViewResolver (resolving to the SampleContentAtomView if needed) or an InternalResourceViewResolver. The snippet below should, by the way, replace the InternalResourceViewResolver that was already configured in your rest-servlet.xml file.

The ContentNegotiatingViewResolver looks at the file extension first and after that the Accept header is used (this is by the way customizable to a certain degree). We have to map the appropriate extensions to the appropriate media types. In this example, we map .html to the media type text/html and we map .atom to application/atom+xml. This will make sure the appropriate views are rendered.

If a .atom request comes in, the ContentNegotiatingViewResolver will look for a view that matches the application/atom+xml media type. The view resolver will look for a view rendering content with media type text/html is an .html request comes in.

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
	<property name="mediaTypes">
		<map>
			<entry key="atom" value="application/atom+xml"/>
			<entry key="html" value="text/html"/>
		</map>
	</property>
	<property name="viewResolvers">
		<list>
			<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
			<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
				<property name="prefix" value="/WEB-INF/jsp/"/>
				<property name="suffix" value=".jsp"/>
			</bean>
		</list>
	</property>
</bean>

<bean id="content" class="com.springsource.samples.rest.SampleContentAtomView"/>

After having put this in your application context, giving the server a restart should do the trick. Go to http://localhost:8080/spring-rest/rest/content.html to view all content items and to generate new ones. Go to http://localhost:8080/spring-rest/rest/content.atom to subscribe to the Atom feed.

I hope this little blog entry showed you how simple it can be to add an Atom feed to your application. In addition to Atom, Spring has view support classes for rendering PDF and Excel files, JSON representations and XML documents. Check them out and let us know what you think!

The downloads

As promised, here are the downloads. Note that the projects are based on a nightly build of Spring. For more recent versions, make of Spring 3.0, make sure to check www.springsource.org.

Similar Posts

Share this Post
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Slashdot
  • Technorati
  • Twitter
 

24 responses


  1. Hi Alef,
    Thanks for the nice post. But the view is being set manually in the controller, right?
    ModelAndView mav = new ModelAndView();
    mav.setViewName("content");

    That means for each content type we should have a separate controller method, if I had the same logic to be rendered as json, atom and marshalling view, I need to write separate methods for each view. Instead of doing that, is it possible to use the content negotiator view resolver to chose different views based on the extension or media types?

    -Teja


  2. Hi Teja,

    I'm confused a bit, because the ContentNegotiatingViewResolver is doing exactly what you are describing and it's explained in the Step 4.

    So in other words, there is no need to define separate controller methods because the view name will be transformed to a view based on the accept header and/or the file extension.

    Let me know if you don't understand, because then I need to rework the blog entry :-)


  3. Sorry, your posting is clear enough.. I got a bit confused… Now I am clear.. Thanks again for the clarification :-)


  4. Hello Alef,
    Thank you for the great post of revealing spring3 features.

    You mentioned that there is a view for JSON. Does Spring3 have it? I couldn't find it.
    Or, you meant spring webflow's spring-js module? (http://jira.springframework.org/browse/SJS-1)

    Currently, I'm using json-lib and it has a view for spring as an extension(json-lib-ext-spring).

    If spring framework itself (not webflow) has a json view support, that would be great!!

    Thanks,


  5. Hello Alef,
    Thank you for the great post of revealing spring3 features.

    You mentioned that there is a view for JSON. Does Spring3 have it? I couldn't find it.
    Or, you meant spring webflow's spring-js module? (http://jira.springframework.org/browse/SJS-1)

    Currently, I'm using json-lib and it has a view for spring as an extension(json-lib-ext-spring).

    If spring framework itself (not webflow) has a json view support, that would be great!!

    Thanks,


  6. Hi, Alef.
    Thanks for a nice post. Sorry for a possibly stupid question, but how exactly is view resolution carried out? How does ContentNegotiatingViewResolver figure out that application/atom xml should be resolved to BeanNameViewResolver and not to InternalResourceViewResolver?
    For example, I tried to delete from the rest-servlet.xml, and when accessing content.atom, while expecting to get the same result as content.html I got the following error:
    Internal error

    ** Root cause is: Could not resolve view with name 'content' in servlet with name 'rest' javax.servlet.ServletException: Could not resolve view with name 'content' in servlet with name 'rest' at …

    Thanks a lot,
    Sergei


  7. Sorry, I meant I tried to delete the line from rest-servlet.xml


  8. @Sergei: I found the answer in the Javadoc, which reads: "Once the requested media type has been determined, this resolver queries each delegate view resolver for a View and determines if the requested media type is compatible with the view's content type). The most compatible view is returned."

    The view's content type is determined by calling getContentType() on the view.

    http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.html


  9. Hi Alef. Nice post. How about an example of let's say 3 representations (with content negotiation as above) of /content.html /content.atom and /content.xml (with the MarshallingView for example) all resolving to the same logical 'content' view. I just don't see how that could be done with the BeanNameViewResolver, etc.

    Thanks,
    Dmitriy.


  10. Hi Alef,

    like Dmitriy I'm also very interested in an example with an additional representation in XML (/content.xml).

    Using BeanNameViewResolver I would need two beans with the same name!?

    Thanks Harald


  11. @Dmitriy

    Good point. I was wondering the same thing — and am pleased it wasn't just me that doesn't see how it can be done. The workaround would be to write some logic to manually parse URLs for the suffixes and then setViewName, but that would defeat the purpose of ContentNegotiatingViewResolver entirely. Surely a bug?


  12. Yes, what is the JSON approach? I looked at the jar from your example and saw the added Atom view, but no JSON view. Please tell us what direction Spring 3 is going to use and what project/package the current implementation is included in.


  13. Dmitriy/Alex,

    I did some digging through the Spring M2 source code… and here's what I found out…

    So I believe the answer as to how Spring supports multiple content types is that all View's have a contentType property associated with them…

    You don't see it in this example because AbstractAtomView has a hardcoded default to "application/atom xml" … MashallingView defaults "application/xml" as it's contentType… although more than likely you'll probably want to set that yourself. Etc.

    So every view has a contentType associated with it… and of course all your views are registered with Spring … and so it will inspect the contentType property of all available views, and made it's decision on which view to render automatically. Thus, you don't need to explicitly configure that mapping. And that's why it looks a little vague in this example. Be sure to check out the petclinic example in M2 … it has a xml MarshallingView included…


  14. I agree with Paul. When will be getting more information on the JacksonJsonView, and why its inclusion in WebFlow rather than mvc. I think json view is much more important than an Atom view. thanks


  15. Concerning JsonView .. I was able to integrate the Json-lib-ext-spring 1.0.1 with REST implementation provided here. add Json as a mediatype of the ContentNegotiatorViewResolver ,

    Still using BeanNameViewResolver, and created a view named SampleContentJsonView extends net.sf.json.spring.web.servlet.view.JsonView

    in the xml file <bean id="content" class=".SampleContentJsonView"/>
    and now when you go to /content.json it will be automatically handled. For future releases, it should be as easy changing the extending class spring will provide.
    Note: if using maven, be sure to exclude spring, spring-web, and spring-webmvc from json-lib-ext-spring


  16. Hi Alef/Andrew,

    I have the same questions as Dmitry, Harald, Alex etc.

    Andrew, your description of how it works does make sense and I saw the petclinic example for M2 and it does not answer the question we're asking.

    Consider the spring-rest app …. If I've to *ADD* an xml or rss view to the *SAME* action /content.

    That would mean bean definitions such as this:

    to satisfy all the content types. But of course, id="content" or for that matter name="content" for all three is not valid. So, how do we resolve this?

    It would also be useful to easily use other view resolvers (rather than bean) and even the same view resolver for all content types(such as say velocity or freemarker)

    One suggestion is for the ContentNegotiatorViewResolver to look for a view name that incorporates the content type, and default back to the current algorithm, otherwise (or some variant thereof).

    For example, in the above cases, the ContentNegotiatorViewResolver could look for a view called content.application.atom xml (or content_applicaton_atom_xml) for atom and content.application.rss xml for rss and content.text.xml for xml etc. Which could be provided by
    content.application.atom xml.jsp (or .vm), content.application.rss xml.jsp, content.text.xml.jsp or content.jsp (For the default text/html)

    The obvious additional benefit here would be to use any (preferred) view technology to render any content type.

    thanks,
    Vijay

    PS: great job with the 3.0. Can't tell you how delighted I am to see the REST support in 3.0. Looking forward to its release.


  17. Oops:

    That would mean bean definitions such as this:

    to satisfy all the content types.

    should've said
    That would mean bean definitions such as this:
    <bean id="content" class="SampleContentAtomView"/>
    <bean id="content" class="SampleContentRssView"/>
    <bean id="content" class="SampleContentXmlView"/>

    to satisfy all the content types.


  18. Hi all,

    I have another problem. I am using the new PathVariable support in addition to the above. So, say I have the following

    @RequestMapping( value="/blogs/{username}"…)

    Now say I want to use ContentNegotiation to support HTML, Atom and RSS views of this user's blog.

    I extract userid as a PathVariable and use it to look up the user by username. But here's the problem. If I then request /blogs/vijay.atom, things blow up. Why? because my extracted username is now vijay.atom!! Obviously, I can deal w/ it in the controller code, but that's ugly. Is there a more appropriate way to handle this?

    thanks,
    Vijay


  19. The sample is good but it doesn't allow more than 1 additional representation.

    We achieved the results we wanted (many content types, content negotiation by request parameter) by extending the BeanNameViewResolver and ContentNegotiatingViewResolver classes. Let me know if you're interested in reviewing the code.

    Our needs will probably require some more extensions to the REST and Content negotiation features so please let us know what's the best place for such discussions and possible contributions.


  20. This is great news to the java world!

    Currently I see two issues though:
    - How to determine format inside controller? (you probably need different data in the different scenarios)
    - How to test the request mappings?

    A standard(Convention over configuration) CRUD request mapping system, like in rails, would also be nice.

    Cheers,

    Stefan


  21. Hi there,

    although this post is already 8 months old, i don't really see the resolution of the problem Dmitriy described above ….

    I'm also experiencing the problem that – besides XML and HTML – i want to support an additional content type and i'm stumbling over the fact that i cannot create more than one bean with the same name.

    Is there any way to realize this within the standard or do i need to extend the existing resolvers like Rostislav proposed?

    Greetz,
    Benjamin


  22. Hi Benjamin,

    the only way to accomplish this is to define the views as "defaultViews" in the ContentNegotiatingViewResolver (see example below):

    Reason is that ContentNegotiatingViewResolver first tries to resolve a view by name and uses the defaultViews after it fails to resolve a view by name (there should be no mappings of REST-Resource URLs to Controllers anyway).

    The name of the Views does not matter, only the supported content types.

    Greets,
    Jens


  23. Great post Alef! I'm just wondering if there's an easy way right now to generate JSONP using Spring MVC.


  24. Hi Everone,

    I am new to this site as well as spring .
    I write on e restful application using spring 3.0 annotation but i failed to run my services ,so please any one help me to how to call my services and i get a following response when i call my service please notice that my error .

    org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.springframework.http.ResponseEntity

    Thanks

3 trackbacks

Leave a Reply