Manage RSS feeds with the Rome API

Sky-Tiger發表於2008-01-06
RSS is a widely used means of publishing syndicated content, from bloglines to build reports. The large number of individual formats (at least six flavors of RSS plus Atom) can make it difficult to manipulate the feeds by hand, however. In this article John Ferguson Smart shows you how to use the Rome API to read and process RSS feeds in any format. You'll also learn how to set up an RSS feed to deliver build reports in a continuous integration environment, using Continuum as your CI server.[@more@]

RSS (Really Simple Syndication) is an established way of publishing short snippets of information, such as news headlines, project releases, or blog entries. Modern browsers such as Firefox, and more recently Internet Explorer 7, and mail clients such as Thunderbird recognize and support RSS feeds; not to mention the a large number of dedicated RSS readers (also known as aggregators) out there.

RSS feeds aren't just for end-users, though. A variety of application scenarios could require you to read, publish, or process RSS feeds from within your code. Your application could need to publish information through a set of RSS feeds, or need to read, and possibly manipulate, RSS data from another source. For example, some applications use RSS feeds to inform users of changes to the application database that could affect them.

RSS feeds also can be useful inside of a development project. Tools like Trac let you use RSS feeds to monitor changes made to a Subversion repository or to a project Web site, which can be a good way to easily keep tabs on the status of many projects simultaneously. Some development projects, for instance, use RSS feeds to monitor continuous integration build results. End users simply subscribe to the CI server's feed in their RSS reader or RSS-enabled Web browser. The server publishes real-time build results in RSS format, which the client can then consult at any time without having to go to the server's Web site.

In this article, I'll show you how to manipulate RSS feeds in Java using the Rome (RSS and Atom utilities) API. I'll also walk you through a concrete application of these techniques, where you'll write a simple class that publishes build results from a Continuum build server in RSS format.

Introducing the Rome API

You could define RSS very simply by saying that it is an XML format that allows you to share information in a condensed manner; for example by publishing only a title, a description, and a link to a more detailed article.

Now, strictly speaking RSS is not a single format, but a family of formats. Among the many flavors of RSS are RSS 0.91 (in Netscape and Userland "sub-flavors"), RSS 0.92, RSS 0.93, RSS 0.94, RSS 1.0, and RSS 2.0. More recently Atom 1.0 has also entered the mix, introducing many subtle and non-so-subtle compatibility issues.

Although you can very well write your own RSS script directly (after all, it's just XML!), doing so forces you to choose, and stick to, one particular format. The issue is even trickier if you have to read and process external RSS feeds, which can come in a variety of formats. This is where an API like Rome comes in handy. In addition to being fluent in the many flavors of RSS, as you will see, the Rome API is easy to use and intuitive to understand, which helps to make coding more productive and code more maintainable.

Rome is an open source Java API for reading and publishing RSS feeds in a relatively format-neutral way. Originally developed by Sun and now a Java.net project, Rome is designed to provide a level of abstraction to mask the subtle differences between the various formats. This lets you concentrate on publishing and/or processing content, rather than wrestling with the particularities of each format. Rome was designed from the onset to be easy to use, flexible, and to support all existing RSS flavors.

Get started with Rome

In this article you'll try your hand at two exercises using the Rome API. First, you'll create a simple feed, populate it, and publish it, with a few small steps in between. Once you have the basics down you can tackle the more advanced exercise of setting up syndicated build reports from a continuous integration server like Continuum.

You'll need to download Rome 0.9, which you can get from the Rome homepage. Rome needs Java 1.4 or higher, and requires JDOM 1.0 for XML processing, which you will also need to download if you don't have it already. If you're using Maven, all you need to do is to add the following dependency to your pom.xml:

rome   
      rome

      0.9
    

Publishing an RSS feed with Rome

Let's start off by creating a new RSS feed and publishing some content. You manipulate an RSS feed by using the SyndFeed interface. True to Rome's charter, SyndFeed provides a common interface for handling all RSS feed types in a uniform manner. It is designed for simplicity rather than universality, and lets you handle any RSS format without having to worry about which RSS flavor you are using. The flip-side of this is that the SyndFeed interface does not support the more exotic fields found in some newer formats. Nevertheless, in practice, SyndFeed is sufficient for the vast majority of RSS programming requirements. Here's how it's done:

Listing 1. A really simple RSS feed

SyndFeed feed = new SyndFeedImpl();
        feed.setFeedType("rss_1.0");
        feed.setTitle("MyProject Build Results");
        feed.setLink(");
        feed.setDescription("Continuous build results for the MyProject project");    
        feed.setCategory("MyProject");

Did you notice the setFeedType() method? It lets you choose your preferred RSS format with a minimum of fuss. Then, you can just get on with coding the usual RSS fields such as title, link, description, and category. These fields are common to all RSS formats, so the SyndFeed class handles them all just fine.

Fields versus formats

As I mentioned, although this interface handles all RSS feed types, it does not support all the fields in those formats. So, when it fetches data from a feed, it may lose some of the specific data from certain RSS formats. There is no way to access these format-specific fields using SyndFeed. For example, the "info" and "language" fields in the Atom formats have no equivalent in other RSS feeds, and are not supported by SyndFeed.

If you do need to exploit the extra features or fields available in a particular format, you can use one of the WireFeed classes. The com.sun.syndication.feed.atom.Feed class, for instance, works for Atom 0.3 and Atom 1.0 feeds. The com.sun.syndication.feed.rss.Channel class handles all RSS versions without loss of data.

Adding an entry

The RSS feed created in Listing 1 does not actually contain any entries yet. You manage entries with the SyndEntry interface. Again, creating a new entry is an intuitive affair, as shown in Listing 2.

Listing 2. Creating a new entry using Rome

SyndEntry entry = new SyndEntryImpl();
        entry.setTitle("BUILD SUCCESSFUL");
        entry.setLink(");
        entry.setPublishedDate(new Date());

The SyndEntry interface has a number of attributes, many of which map to different fields in the actual RSS feed, depending on the format used. Like the SyndFeed interface, SyndEntry is a general interface, compatible with all RSS formats. It does not support all the format-specific features available for each format, however. If you are using one of the format-specific WireFeed classes described above, you will need to use a more specific entry object, such as com.sun.syndication.feed.atom.Feed for Atom entries or com.sun.syndication.feed.rss.Item for the RSS versions.

Descriptions

Next, you need to add the actual entry description, using the SyndContent interface. The setType() method is used to specify the type of content you are going to place in the description: plain text ("text/plain"), HTML ("text/html"), or XML ("text/xml"):

Listing 3. Adding a description

SyndContent description = new SyndContentImpl();
        description.setType("text/html");
        description.setValue("The build was successful!");
        entry.setDescription(description);

Categories

You can also define categories for your entry. Categories are widely used in RSS feeds to class entries into what the RSS people call a "categorization taxonomy." In order to be meaningful, categories generally come from a pre-defined list of possible values for a given RSS feed. An entry can have any number of categories, which allows you to cross-reference an entry in several different categories. Rome represents categories with the SyndCategory interface, as shown here:

Listing 4. Categorizing the entry

List categories = new ArrayList();
        SyndCategory category = new SyndCategoryImpl();
        category.setName("MyProject");
        categories.add(category);
        entry.setCategories(categories);

Set and publish

Once you have obtained some entries, you can add them to your SyndFeed object, using the setEntries() method, like so:

feed.setEntries(entries);

And now you have an RSS feed ready to go! To write your RSS feed to an output stream, just use the SyndFeedOutput class, as shown in Listing 5.

Listing 5. Publishing the feed

Writer writer = new FileWriter("stream.xml");
        SyndFeedOutput output = new SyndFeedOutput();
        output.output(feed,writer);
      writer.close();

Reading and updating an external RSS feed

Rome is not only good at writing RSS feeds: in fact, it also excels at reading and manipulating them. This is one of the things that makes Rome a good choice if you need to provide any aggregation features in your applications.

You can read an RSS feed from any source (URL, file, and so on) using the SyndFeedInput and XmlReader classes, as shown in Listing 6.

Listing 6. Loading a feed with SyndFeedInput

URL feedSource = new URL(");
        SyndFeedInput input = new SyndFeedInput();
        SyndFeed feed = input.build(new XmlReader(feedSource));

Once you have loaded the SyndFeed object, you can modify the RSS entries or add new ones, as shown above. Note that you are modifying your local copy of the feed, not the original feed. When you're done, you will probably need to write the new feed to disk using the SyndFeedOutput class.

The eternal city: Integrating Rome and Continuum

You've seen how to use Rome to easily publish an RSS feed. Now let's apply it in the context of a realistic development project, such as generating an RSS feed from a continuous integration server. Some CI servers, such as Hudson, come with RSS feeds built in, but not Continuum -- at least not just yet. Continuum does come with a convenient API that allows you to access the server from an external Java application, however. You can put this API to good use to provide an RSS feed that details build results.

It is fairly easy to write a class that monitors build results and creates a new RSS entry whenever a new build is executed. In the next sections you'll go through the steps involved in creating such a class. I've simplified some of the code shown here to make reading easier, and also removed comments and error-handling code. See the source code download for full-fledged samples.

The RssPublisher class

The monitor class, called RssPublisher, is a simple POJO-style Java bean. Its only dependencies are to the Rome jars, the Continuum XML-RPC client classes, and the Jakarta Commons CLI libraries (to handle command-line options). The class uses some JavaBeans-style attributes (the getters and setters are not shown) to provide configuration details and to customize its behavior. RssPublisher is shown in Listing 7.

Listing 7. RssPublisher

public class RssPublisher implements Serializable { private String rssFeedFilename = "rss/builds.xml"; private int updateFrequency = 30; private String continuumXMLRPCUrl = " private String rssFormat = "rss_1.0"; private String rssFeedTitle = "Company Build Results"; ... }

Read and update the RSS feed

The next step is to read and update the RSS feed whenever a project's build status changes. You do this in the updateLatestBuildResults() method, which uses a Map object to store the state of each managed project, like so:

Listing 8. updateLatestBuildResults()

private Map previousState = new HashMap();

public void updateLatestBuildResults() throws IOException, FeedException, XmlRpcException {
    ...


This method begins by preparing the RSS feed, creating it if necessary.

Listing 9. Preparing the RSS feed

SyndFeed feed = null;
        File feedSource = new File(getRssFeedFilename());
        if (feedSource.exists()) {
            SyndFeedInput input = new SyndFeedInput();
            feed = input.build(new XmlReader(feedSource));            
        } else {
            feed = new SyndFeedImpl();
            feed.setFeedType(getRssFormat());
            feed.setTitle(getRssFeedTitle());
            feed.setLink(getContinuumServerUrl());
            feed.setDescription("Continuum build results");
        }

Once you've obtained the RSS feed, you need to fetch, or create, the list of RSS entries for it.

Listing 10. Fetching or creating entries

List entries = feed.getEntries();
        if (entries == null) {
            entries = new ArrayList();
        }

Ready, set, connect

Your next step is to obtain the list of projects being built on the Continuum server. To do this, you use the Continuum RPC library. Including this library in a Maven project is easy to do with the following dependency declaration:

Listing 11. Adding the Continuum RPC library to your dependencies

  
      org.apache.maven.continuum 
      continuum-xmlrpc-client   
      1.1-beta-1    
    


Next, you connect to the Continuum server, using the ContinuumXmlRpcClient class and the special "xmlrpc" URL.

Listing 12. ContinuumXmlRpcClient

ContinuumXmlRpcClient client 
                = new ContinuumXmlRpcClient(
                       new URL("),
                       getUsername(),getPassword());


You will need to provide an administrator-level username and password here. You then fetch the list of project groups and projects for each group.

Listing 13. Fetching project groups and projects

List groups = client.getAllProjectGroups();
            for (ProjectGroup group : groups) {
            List projects = client.getProjects(group.getId());
                    for (Project project : projects) {


The build report

With Rome and Continuum talking, you can get to the heart of the matter, which is comparing the current state of each project to the previous project state. If the project state has changed, you may need to report it, as shown in Listing 14.

Listing 14. Reporting state change

int state = project.getState();
                int prevState = -1;
                if (previousState.containsKey(project.getName())) {
                    prevState = previousState.get(project.getName());
                }
                if (prevState != state) {
                    previousState.put(project.getName(), state);
                    String buildStateMessage = null;
                    if ((state == 1) || (state == 10)) {
                        buildStateMessage = "New build";
                    } else if (state == 2) {
                        buildStateMessage = "Successful build";
                    } else if (state == 3) {
                        buildStateMessage = "Failed build";
                    } else if (state == 4) {
                        buildStateMessage = "Build error";
                    }

If a significant state change has occurred, you create a new RSS entry with a link to the build results and add it to your RSS feed. Finally, you write the new RSS feed to the disk.

Listing 15. Writing to disk

if (buildStateMessage != null) {
                        SyndEntry entry = new SyndEntryImpl();
                        entry.setTitle(project.getName() + " : "
                                + buildStateMessage);
                        entry
                                .setLink(getContinuumServerUrl()
                                        + "/servlet/continuum/target/ProjectBuild.vm?view=ProjectBuild&buildId="
                                        + project.getLatestBuildId() + "&id="
                                        + project.getId());

                        entry.setPublishedDate(new Date());
                        entries.add(entry);
                    }
                }
            }
        }
        feed.setEntries(entries);
        Writer writer = new FileWriter(getRssFeedFilename());
        SyndFeedOutput output = new SyndFeedOutput();
        output.output(feed,writer);
        writer.close();
}

Now you need to call this method at regular intervals, to keep tabs on the build results. The startScheduler() method starts a Timer instance that does just that, based on the updateFrequency property.

Listing 16. Scheduler goes to work

public void startScheduler() {
        if (timer != null) {
            timer.cancel();
        }
        timer = new Timer();
        timer.schedule(new TimerTask() {
            public void run() {
                try {
                    updateLatestBuildResults();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 0, updateFrequency * 1000);
    }

All you have to do now is create an instance of the bean and call this method to start the monitoring process. The sample source code also allows you to run the class from the command line, and to specify properties as command-line options.

The RssPublisher class will now produce an RSS feed and send it, by default, to a file called rss/builds.xml. Publishing the feed to a Web server is straightforward -- any old Web server, such as Tomcat, Jetty, or Apache, will do the job. The simplest approach is to run your feed class on the home (or some other) directory of your Web server. It will monitor the Continuum server and update the rss/builds.xml file whenever necessary. Users can subscribe to the feed just as they would any other RSS feed.

In conclusion

Rome's charter is to end syndication feed confusion by supporting all formats, meaning the many variants of RSS as well as Atom. In this article I've introduced you to the Rome API and shown you how to use it to create and manage RSS feeds. I've also walked you through the practical and useful example of setting up syndicated feeds for a continuous integration server. While some CI servers (such as Hudson) come with RSS built in, others like Continuum still require some hand tooling. After reading this article you should be feeling pretty comfortable with the Rome API. See the source code download if you want to play around with the CI server class some more.

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/71047/viewspace-996760/,如需轉載,請註明出處,否則將追究法律責任。

相關文章