Writing your own renderRSS method as controller output

Nowadays, an almost standard feature of web applications is to provide RSS feeds, irrespective of whether it is for a blog or some location-based service. Most clients can handle RSS out of the box. The Play examples only carry an example with hand crafted RSS feeds around. This example shows how to use a library for automatic RSS feed generation by getting the newest 20 post entities and rendering it either as RSS, RSS 2.0 or Atom feed.

You can find the source code of this example in the chapter2/render-rss directory.

Getting ready

As this recipe makes use of the ROME library to generate RSS feeds, you need to download ROME and its dependency JDOM first. You can use the Play dependency management feature again. Put this in your conf/dependencies.yml:

require:
    - play
    - net.java.dev.rome -> rome 1.0.0

Now as usual a test comes first:

public classFeedTest extends FunctionalTest {

    @Test
    public void testThatRss10Works() throws Exception {
        Response response = GET("/feed/posts.rss");
        assertIsOk(response);
        assertContentType("application/rss+xml", response);
        assertCharset("utf-8", response);
        SyndFeed feed = getFeed(response);
        assertEquals("rss_1.0", feed.getFeedType());
    }

    @Test
    public void testThatRss20Works() throws Exception {
        Response response = GET("/feed/posts.rss2");
        assertIsOk(response);
        assertContentType("application/rss+xml", response);
        assertCharset("utf-8", response);
        SyndFeed feed = getFeed(response);
        assertEquals("rss_2.0", feed.getFeedType());
    }

    @Test
    public void testThatAtomWorks() throws Exception {
        Response response = GET("/feed/posts.atom");
        assertIsOk(response);
        assertContentType("application/atom+xml", response);
        assertCharset("utf-8", response);
        SyndFeed feed = getFeed(response);
        assertEquals("atom_0.3", feed.getFeedType());
    }

    private SyndFeedgetFeed(Response response) throws Exception {
        SyndFeedInput input = new SyndFeedInput();
        InputSource s = new InputSource(IOUtils.toInputStream(getContent(response))); 
        return input.build(s);
    }
}

This test downloads three different kinds of feeds, rss1, rss2, and atom feeds, and checks the feed type for each. Usually you should check the content as well, but as most of it is made up of random chars at startup, it is dismissed here.

How to do it...

The first definition is an entity resembling a post:

@Entity
public class Post extends Model {

    public String author;
    public String title;
    public Date createdAt;
    public String content;

    public static List<Post>findLatest() {
        return Post.findLatest(20);
    }

    public static List<Post>findLatest(int limit) {
        return Post.find("order by createdAt DESC").fetch(limit);
    }
}

A small job to create random posts on application startup, so that some RSS content can be rendered from application start:

@OnApplicationStart
public class LoadDataJob extends Job {

    // Create random posts
    public void doJob() {
        for (int i = 0 ; i < 100 ; i++) {
            Post post = new Post();
            post.author = "Alexander Reelsen";
            post.title = RandomStringUtils.randomAlphabetic(RandomUtils.nextInt(50));
            post.content = RandomStringUtils.randomAlphabetic(RandomUtils.nextInt(500));
            post.createdAt = new Date(new Date().getTime() + RandomUtils.nextInt(Integer.MAX_VALUE));
            post.save();
        }
    }
}

You should also add some metadata in the conf/application.conf file:

rss.author=GuybrushThreepwood
rss.title=My uber blog
rss.description=A blog about very cool descriptions

The routes file needs some controllers for rendering the feeds:

GET     /                 Application.index
GET     /feed/posts.rss   Application.renderRss
GET     /feed/posts.rss2  Application.renderRss2
GET     /feed/posts.atom  Application.renderAtom
GET     /post/{id}        Application.showPost

The Application controller source code looks like this:

import static render.RssResult.*;

public class Application extends Controller {

    public static void index() {
          List<Post> posts = Post.findLatest(100);
        render(posts);
    }

    public static void renderRss() {
          List<Post> posts = Post.findLatest();
        renderFeedRss(posts);
    }

    public static void renderRss2() {
          List<Post> posts = Post.findLatest();
        renderFeedRss2(posts);
    }

    public static void renderAtom() {
          List<Post> posts = Post.findLatest();
        renderFeedAtom(posts);
    }

    public static void showPost(Long id) {
          List<Post> posts = Post.find("byId", id).fetch();
          notFoundIfNull(posts);
          renderTemplate("Application/index.html", posts);
    }
}

You should also adapt the app/views/Application/index.html template to show posts and to put the feed URLs in the header to make sure a browser shows the RSS logo on page loading:

#{extends 'main.html' /}
#{set title:'Home' /}
#{set 'moreHeaders' }
<link rel="alternate" type="application/rss+xml" title="RSS 1.0 Feed" href="@@{Application.renderRss2()}" />
<link rel="alternate" type="application/rss+xml" title="RSS 2.0 Feed" href="@@{Application.renderRss()}" />
<link rel="alternate" type="application/atom+xml" title="Atom Feed" href="@@{Application.renderAtom()}" />
#{/set}

#{list posts, as:'post'}
<div>
<h1>#{a @Application.showPost(post.id)}${post.title}#{/a}</h1><br />
by ${post.author} at ${post.createdAt.format()}
<div>${post.content.raw()}</div>
</div>
#{/list}

You also have to change the default app/views/main.html template, from which all other templates inherit to include the moreHeaders variable:

<html>
    <head>
        <title>#{get 'title' /}</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        #{get 'moreHeaders' /}
        <link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}">
    </head>
    <body>
        #{doLayout /}
    </body>
</html>

The last part is the class implementing the different renderFeed methods. This is again a Result class:

public class RssResult extends Result {

    private List<Post> posts;
    private String format;

    public RssResult(String format, List<Post> posts) {
        this.posts = posts;
        this.format = format;
    }

    public static void renderFeedRss(List<Post> posts) {
        throw new RssResult("rss", posts);
    }

    public static void renderFeedRss2(List<Post> posts) {
        throw new RssResult("rss2", posts);
    }

    public static void renderFeedAtom(List<Post> posts) {
        throw new RssResult("atom", posts);
    }

    public void apply(Request request, Response response) {
        try {
            SyndFeed feed = new SyndFeedImpl();
            feed.setAuthor(Play.configuration.getProperty("rss.author"));
            feed.setTitle(Play.configuration.getProperty("rss.title"));
            feed.setDescription(Play.configuration.getProperty("rss.description"));
            feed.setLink(getFeedLink());

            List<SyndEntry> entries = new ArrayList<SyndEntry>();
            for (Post post : posts) {
              String url = createUrl("Application.showPost", "id", post.id.toString());
              SyndEntry entry = createEntry(post.title, url, post.content, post.createdAt);
              entries.add(entry);
            }

            feed.setEntries(entries);

            feed.setFeedType(getFeedType());
            setContentType(response);

              SyndFeedOutput output = new SyndFeedOutput();
              String rss = output.outputString(feed);
              response.out.write(rss.getBytes("utf-8"));
        } catch (Exception e) {
              throw new UnexpectedException(e);
        }
    }

    private SyndEntrycreateEntry (String title, String link, String description, Date createDate) {
        SyndEntry entry = new SyndEntryImpl();
        entry.setTitle(title);
        entry.setLink(link);
        entry.setPublishedDate(createDate);

        SyndContententryDescription = new SyndContentImpl();
        entryDescription.setType("text/html");
        entryDescription.setValue(description);

        entry.setDescription(entryDescription);

        return entry;
    }

    private void setContentType(Response response) {
        ...
    }

    private String getFeedType() {
        ...
    }

    private String getFeedLink(){
        ...
    }

    private String createUrl(String controller, String key, String value) {
        ...
    }
}

How it works...

This example is somewhat long at the end. The post entity is a standard model entity with a helper method to find the latest posts. The LoadDataJob fills the in-memory database on startup with hundreds of random posts.

The conf/routes file features showing an index page where all posts are shown, as well as showing a specific post and of course showing all three different types of feeds.

The controller makes use of the declared findLatest() method in the post entity to get the most up-to-date entries. Furthermore the showPost() method also utilizes the index.html template so you do not need to create another template to view a single entry. All of the used renderFeed methods are defined in the FeedResult class.

The index.html template file features all three feeds in the header of the template. If you take a look at app/views/main.html, you might notice the inclusion of the moreHeaders variable in the header. Using the @@ reference to a controller in the template creates absolute URLs, which can be utilized by any browser.

The FeedResult class begins with a constructor and the three static methods used in the controller, which render RSS, RSS 2.0, or Atom feeds appropriately.

The main work is done in the apply() method of the class. A SyndFeed object is created and filled with meta information like blog name and author defined in the application.conf file.

After that a loop through all posts generates entries, each including the author, content, title, and the generated URL. After setting the content type and the feed type—RSS or atom—the data is written to the response output stream.

The helper methods have been left out to save some lines inside this example. The setContentType() method returns a specific content type, which is different for RSS and atom feeds. The getFeedType() method returns "rss_2.0", "rss_1.0", or "atom_0.3" depending on the feed to be returned. The getFeedLink() method returns an absolute URL for any of the three feed generating controller actions. The createUrl() method is a small helper to create an absolute URL with a parameter which in this case is an ID. This is needed to create absolute URLs for each post referenced in the feed.

The example also uses ROME to extract the feed data again in the test, which is not something you should do to ensure the correct creation of your feed. Either use another library, or, if you are proficient in checking corner cases by hand, do it manually.

There's more...

This is (as with most of the examples here) only the tip of the iceberg. Again, you could also create a template to achieve this, if you wanted to keep it simple. The official documentation lists some of the preparing steps to create your own templates ending with .rss at http://www.playframework.org/documentation/1.2/routes#Customformats

Using annotations to make your code more generic

This implementation is implementation specific. You could make it far more generic with the use of annotations at the Post entity:

@Entity
public class Post extends Model {

    @FeedAuthor
    public String author;
    @FeedTitle
    public String title;
    @FeedDate
    public Date createdAt;
    @FeedContent
    public String content;
}

Then you could change the render signature to the following:

    public RssResult(String format, List<? extends Object> data) {
        this.data = data;
        this.format = format;
    }

    public static void renderFeedRss(List<? extends Object> data) {
        throw new RssResult("rss", data);
    }

By using the generics API you could check for the annotations defined in the Post entity and get the content of each field.

Using ROME modules

ROME comes with a bunch of additional modules. It is pretty easy to add GeoRSS information or MediaWiki-specific RSS. This makes it pretty simple to extend features of your feeds.

Cache at the right place

Especially RSS URLs are frequently under heavy loads – one misconfigured client type can lead to some sort of denial of service attack on your system. So again a good advice is to cache here. You could cache the SyndFeed object or the rss string created out of it. However, since play 1.1 you could also cache the complete controller output. See Basics of caching at the beginning of Chapter 4 for more information about that.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.75.70