romelogo-small.jpg

For my current customer I had to create an rss feed. In the java domain you immediately grab Rome to do the job. There was a catch. My customer wants (with good reason) to have feeds validated by the w3c feed validator. This turned out to be a slightly more complicated job. Luckily Rome has good support for extensions, so at least it was possible.

In this blog post I describe the challenges I had creating the validated feed. If you want more in depth information please check my post on my employers blog : serving a heavy load rss feed with spring 3 and ehcache.

Rome extension

At the moment I am missing the atom namespace in the resulting xml.

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">

I also miss the actual atom link:

<atom:link href="http://dallas.example.com/rss.xml" rel="self" type="application/rss+xml" />

Now we must tell Rome how to generate this content. Rome supports a plugin structure. Detailed information can be found on this page:

http://wiki.java.net/bin/view/Javawsxml/Rome05TutorialSampleModule

An extension consists of a module containing the data to be used by the extension, a generator if the extension needs to add items to the generated feed and a parser if the extension is about reading feeds. In my case I only need the module component and the generator. Finally you need to tell Rome about the extension, this is done in a configuration file that needs to be available on the classpath.

Module

The only data I need is the link for the atom element, the href. The module itself consists of an interface and an implementation. The interface defines the getters and setters. The implementation has a few additional methods. The following code block shows them both.

public interface AtomNSModule extends Module {
    public static final String URI = "http://www.w3.org/2005/Atom";
    String getLink();
    void setLink(String href);
}

public class AtomNSModuleImpl extends ModuleImpl implements AtomNSModule {
    private String link;

    public AtomNSModuleImpl() {
        super(AtomNSModule.class, URI);
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public Class getInterface() {
        return AtomNSModule.class;
    }

    public void copyFrom(Object obj) {
        AtomNSModule module = (AtomNSModule) obj;
        module.setLink(this.link);
    }
}

The code is not to hard to understand. Concentrate on the copyForm method. Here we put the data from our model into the object that is used by the framework to extract data. This is why you need the interface as well.

Generator

Here we add the elements to the generated xml. JDom is used to generate xml from an object tree of Elements. The following code block shows the complete generator.

public class AtomNSModuleGenerator implements ModuleGenerator {
    private static final Namespace ATOM_NS = Namespace.getNamespace("atom", AtomNSModule.URI);

    private static final Set NAMESPACES;

    static {
        Set nss = new HashSet();
        nss.add(ATOM_NS);
        NAMESPACES = Collections.unmodifiableSet(nss);
    }

    public String getNamespaceUri() {
        return AtomNSModule.URI;
    }

    public Set getNamespaces() {
        return NAMESPACES;
    }

    public void generate(Module module, Element element) {
        AtomNSModule atomNSModule = (AtomNSModule) module;
        Element root = element;
        while (root.getParent() != null && root.getParent() instanceof Element) {
            root = (Element) element.getParent();
        }
        root.addNamespaceDeclaration(ATOM_NS);

        Element atomLink = new Element("link", ATOM_NS);
        atomLink.setAttribute("href", atomNSModule.getLink());
        atomLink.setAttribute("rel", "self");
        atomLink.setAttribute("type", "application/rss+xml");

        element.addContent(0, atomLink);
    }
}

Focus on the generate method. In this method we add the namespace declaration to the root element and we create a new element that is added to the provided element. The provided element is the channel element. The result of the feed now looks like this.

<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <atom:link href="http://localhost:8080/rss/nieuws.rss" rel="self" type="application/rss+xml" />
	...
  </channel>
</rss>

Configuration

The last part is telling Rome to use the new component. This is done by putting a rome.properties file on the classpath with the following contents.

rss_2.0.feed.ModuleGenerator.classes=nl.gridshore.rss.romeextension.AtomNSModuleGenerator

It is important to specify the rss_2.0 because multiple generators are available and this is the one that is used.

Encoding – response type

Another problem I had had to do with encoding. You can set the type of encoding in the generated xml, but the response of the spring controller/view combination does not follow this. Therefore you have to set the encoding of the response object explicitly. In the view component I added the following lines of code:

    @Override
    protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
        super.prepareResponse(request, response);
        response.setCharacterEncoding(CharacterEncodingConstants.UTF8);
    }

I hope this can help others to create better feeds as well, suggestions for improvements are of course welcome.

Creating a w3c validated rss feed using Rome and spring 3
Tagged on: