Spring MVC RSS ATOM Content Negotiation Example

In this tutorial we show a Spring MVC RSS ATOM Content Negotiation example. Using Content Negotiation we can serve different versions of a document (or resource) at the same URI. Depending on the HTTP Accept header, a path file extension in the URL, a specific request parameter we return a view accordingly or if none of the previous are presented, we fall back on a default view.

Rss and Atom are used to create web feeds. These feeds are used to publish frequently updated information, typically found on blogs, news sites etc. These feeds contain information about the article like: title, link, description, content, etc.. Later on we create an RSS View, an Atom view and a JSP view, depending on the previous parameters we serve the correct view.

Maven Dependencies

We use Apache Maven to manage our project. Add the following dependencies and maven will automatically resolve them for us. Make sure the com.rometools:rome dependency resides on the classpath. We are using this library to create the Rss and Atom views.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.memorynotfound.spring.mvc</groupId>
    <artifactId>rss-view-resolver</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>SPRING-MVC - ${project.artifactId}</name>
    <url>http://memorynotfound.com</url>
    <packaging>war</packaging>

    <properties>
        <encoding>UTF-8</encoding>
        <spring.version>4.2.6.RELEASE</spring.version>
    </properties>

    <dependencies>
        <!-- spring libraries -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- rss and atom -->
        <dependency>
            <groupId>com.rometools</groupId>
            <artifactId>rome</artifactId>
            <version>1.6.0</version>
        </dependency>

        <!-- servlet api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Spring MVC RSS ATOM Content Negotiation – Configuration

When we extend from the WebMvcConfigurerAdapter class, we can override the configureContentNegotiation(...) method. Using this method we can configure the content negotiation using the ContentNegotiationConfigurer. We can modify the default content type that’ll be served when none of the previous parameters are passed in the request. Here we can also give a priority to the different strategies.

Next, we register the different view which we’ll create later in this tutorial.

package com.memorynotfound.config;

import com.memorynotfound.view.AtomView;
import com.memorynotfound.view.RssView;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.*;

@EnableWebMvc
@Configuration
@ComponentScan("com.memorynotfound")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
                .defaultContentType(MediaType.TEXT_HTML)
                .parameterName("type")
                .favorParameter(true)
                .ignoreUnknownPathExtensions(false)
                .ignoreAcceptHeader(false)
                .useJaf(true);
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
        registry.enableContentNegotiation(
                new RssView(),
                new AtomView());
    }

}

This is the equivalent Spring XML Configuration.

<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:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:annotation-driven/>
    <context:component-scan base-package="com.memorynotfound" />

    <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
        <property name="defaultContentType" value="TEXT_HTML"/>
        <property name="parameterName" value="type"/>
        <property name="favorParameter" value="true"/>
        <property name="ignoreUnknownPathExtensions" value="false"/>
        <property name="ignoreAcceptHeader" value="false"/>
        <property name="useJaf" value="true"/>
    </bean>

    <mvc:view-resolvers>
        <mvc:content-negotiation>
            <mvc:default-views>
                <bean class="com.memorynotfound.view.RssView"/>
                <bean class="com.memorynotfound.view.AtomView"/>
            </mvc:default-views>
        </mvc:content-negotiation>
        <mvc:jsp prefix="/WEB-INF/views/" suffix=".jsp"/>
    </mvc:view-resolvers>

</beans>

The DispatcherServlet needs to be registered. This servlet will acts like a front-controller, dispatching the requests to the correct controller methods annotated with the @RequestMapping. We configure the DispatcherServlet programatically by extending from the AbstractAnnotationConfigDispatcherServletInitializer and pass the location of the spring configuration file.

package com.memorynotfound.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }
}

RSS Document

To demonstrate a simple Rss or Atom feed, we created a simple Document POJO with some basic meta-data. Typically this would be an article, blog post or other information that can be shared.

package com.memorynotfound.model;

import java.util.Date;

public class Document {

    private String feedId;
    private String title;
    private String description;
    private Date pubDate;
    private String link;

    public Document(String feedId, String title, String description, Date pubDate, String link) {
        this.feedId = feedId;
        this.title = title;
        this.description = description;
        this.pubDate = pubDate;
        this.link = link;
    }

    public String getFeedId() {
        return feedId;
    }

    public String getTitle() {
        return title;
    }

    public String getDescription() {
        return description;
    }

    public Date getPubDate() {
        return pubDate;
    }

    public String getLink() {
        return link;
    }
}

RSS View

Create an Rss View by extending from the AbstractRssView and override the buildFeedMetadata and buildFeedItems methods. These methods populate the rss feed.

package com.memorynotfound.view;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.memorynotfound.model.Document;
import org.springframework.web.servlet.view.feed.AbstractRssFeedView;
import com.rometools.rome.feed.rss.Channel;
import com.rometools.rome.feed.rss.Content;
import com.rometools.rome.feed.rss.Item;

public class RssView extends AbstractRssFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model,
                                     Channel channel,
                                     HttpServletRequest request) {

        channel.setTitle("Spring MVC RSS ATOM Content Negotiation Example");
        channel.setLink("http://memorynotfound.com/spring-mvc-rss-atom-content-negotiation/");
        channel.setDescription("RSS ATOM View Resolvers");
        channel.setLastBuildDate(new Date());
        channel.setTtl(1800);
    }

    @Override
    @SuppressWarnings("unchecked")
    protected List<Item> buildFeedItems(Map<String, Object> model,
                                        HttpServletRequest request,
                                        HttpServletResponse response) throws Exception {

        List<Item> items = new ArrayList<Item>();
        Object ob = model.get("documents");
        if (ob instanceof List){
            List<Document> documents = (List<Document>)ob;
            for (Document document : documents){

                // content item
                Content content = new Content();
                content.setValue(document.getDescription());

                // item
                Item item = new Item();
                item.setTitle(document.getTitle());
                item.setContent(content);
                item.setPubDate(document.getPubDate());
                item.setLink(document.getLink());
                items.add(item);
            }
        }
        return items;
    }
}

ATOM View

Create an Atom view by extending from the AbstractAtomFeedView and override the buildFeedMetadata and buildFeedEntries methods. These methods populate the atom feed.

package com.memorynotfound.view;

import com.memorynotfound.model.Document;
import com.rometools.rome.feed.atom.Content;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.feed.synd.SyndPerson;
import com.rometools.rome.feed.synd.SyndPersonImpl;
import org.springframework.web.servlet.view.feed.AbstractAtomFeedView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

public class AtomView extends AbstractAtomFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model, Feed feed,
                                     HttpServletRequest request) {

        // content
        Content content = new Content();
        content.setValue("ATOM View");
        feed.setSubtitle(content);
        feed.setTitle("Spring MVC RSS ATOM Content Negotiation Example");

        // links
        Link link = new Link();
        link.setHref("http://memorynotfound.com/feed");
        link.setRel("self");
        link.setTitle("ATOM feed URL");
        feed.setAlternateLinks(Arrays.asList(link));

        // meta-data
        feed.setUpdated(new Date());
    }

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

        List<Entry> entries = new ArrayList<Entry>();
        Object ob = model.get("documents");
        if (ob instanceof List) {
            List<Document> documents = (List<Document>) ob;
            for (Document document : documents) {

                // entry
                Entry entry = new Entry();
                entry.setId(document.getFeedId());
                entry.setPublished(document.getPubDate());
                entry.setTitle(document.getTitle());

                // content
                Content content = new Content();
                content.setValue(document.getDescription());
                entry.setSummary(content);

                // links
                Link link = new Link();
                link.setHref(document.getLink());
                entry.setAlternateLinks(Arrays.asList(link));

                // authors
                SyndPerson author = new SyndPersonImpl();
                author.setName("Memorynotfound");
                author.setEmail("[email protected]");
                entry.setAuthors(Arrays.asList(author));

                entries.add(entry);
            }
        }

        return entries;
    }
}

Controller Endpoint

The DocumentController initializes a list of documents and passes the documents to the Model. Note that no code is added here to server different views, this is all handled by the content negotiation mechanism of Spring MVC which we configured earlier.

package com.memorynotfound.controller;

import com.memorynotfound.model.Document;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Controller
@RequestMapping("/documents")
public class DocumentController {

    List<Document> documents = Arrays.asList(
            new Document("1", "title1", "description1", new Date(), "http://memorynotfound.com"),
            new Document("2", "title2", "description2", new Date(), "http://memorynotfound.com")
    );

    @RequestMapping(method = RequestMethod.GET)
    public String getDocuments(Model model) {
        model.addAttribute("documents", documents);
        return "index";

    }
}

Content Negotiation Demo

Next, we demonstrate how we can use the content negotiation we configured earlier. Like we mentioned in the introduction, we can negotiate content in different ways. Using either the HTTP Accept header, a path file extension in the URL, a specific request parameter or the default.

HTTP Accept Header

We request an Rss feed by passing the application/rss+xmlwhich is the Mime type of Rss – to the HTTP Accept Header.

curl -H "Accept: application/rss+xml" http://localhost:8081/spring-mvc-rss-atom/documents

The previous curl command gives us the following response.

<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>Spring MVC RSS ATOM Content Negotiation Example</title>
    <link>http://memorynotfound.com/spring-mvc-rss-atom-content-negotiation/</link>
    <description>RSS ATOM View Resolvers</description>
    <lastBuildDate>Thu, 19 May 2016 08:16:05 GMT</lastBuildDate>
    <ttl>1800</ttl>
    <item>
      <title>title1</title>
      <link>http://memorynotfound.com</link>
      <content:encoded>description1</content:encoded>
      <pubDate>Thu, 19 May 2016 08:15:22 GMT</pubDate>
    </item>
    <item>
      <title>title2</title>
      <link>http://memorynotfound.com</link>
      <content:encoded>description2</content:encoded>
      <pubDate>Thu, 19 May 2016 08:15:22 GMT</pubDate>
    </item>
  </channel>
</rss>

We request an Atom feed by passing the application/atom+xmlwhich is the Mime type of Atom – to the HTTP Accept Header.

curl -H "Accept: application/atom+xml" http://localhost:8081/spring-mvc-rss-atom/documents

The previous curl command gives us the following response.

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Spring MVC RSS ATOM Content Negotiation Example</title>
  <link rel="self" href="http://memorynotfound.com/feed" title="ATOM feed URL" />
  <subtitle>ATOM View</subtitle>
  <updated>2016-05-19T08:15:28Z</updated>
  <entry>
    <title>title1</title>
    <link rel="alternate" href="http://memorynotfound.com" />
    <author>
      <name>Memorynotfound</name>
      <email>[email protected]</email>
    </author>
    <id>1</id>
    <published>2016-05-19T08:15:22Z</published>
    <summary>description1</summary>
  </entry>
  <entry>
    <title>title2</title>
    <link rel="alternate" href="http://memorynotfound.com" />
    <author>
      <name>Memorynotfound</name>
      <email>[email protected]</email>
    </author>
    <id>2</id>
    <published>2016-05-19T08:15:22Z</published>
    <summary>description2</summary>
  </entry>
</feed>

Default

URL: http://localhost:8081/spring-mvc-rss-atom/documents

spring mvc rss atom content negotiation default

File extension in URL

URL: http://localhost:8081/spring-mvc-rss-atom/documents.atom

spring mvc rss atom content negotiation atom

Request Parameter

URL: http://localhost:8081/spring-mvc-rss-atom/documents?type=rss

spring mvc rss atom content negotiation rss

References

Download

You may also like...