Implementing RSS Feeds with new features of Spring 3
In this post I explain how we implemented the way we created the RSS feeds in a project and the challenges that we had during the set-up.
My colleague Jettro Coenradie explained in a previous post how you can create a feed using Rome and Spring 3, but didn’t elaborated on the Spring 3 part. I will explain how we used Spring 3 to create the feeds.
Context
In this project we need to support a large number of different feeds. As an example I will use the news feed that we need to create for different subjects. The subjects are created in a CMS and therefore people can add subjects whenever they want. This means that we want to support dynamically created feeds.
Controller
The controller is the place where the request mapping takes place.
@Controller public class SubjectRssController extends AbstractRssController { @SessionManagedDaos @RequestMapping(value = "/subjects/{subjectName}/news.rss", method = RequestMethod.GET) public String generateSubjectNewsFeedItems(ModelMap modelMap, @PathVariable final String subjectName, HttpServletRequest request, RssSubjectDao subjectDao) throws ServletException { SubjectBean subject = findSubject(subjectName, request, subjectDao); RssQueryResultMapper mapper = getSubjectQueryResultMapper(); List<item> items = subjectDao.findNewsForSubject(subject, mapper); modelMap.addAttribute("items", items); modelMap.addAttribute("subjectName", subject.getTitle()); return "subjectRssFeed"; }
Annotations
The first thing you might notice when you look at the code above are the two annotations (@SessionManagedDaos and @RequestMapping) on the method. The @SessionManagedDaos is an annotation that is created by us. This annotation manages the session that we use from the CMS connection pool.
The second annotation is the @RequestMapping. This annotation has the value “/subjects/{subjectName}/news.rss” which means that every path that matches this pattern will be mapped to this method. In this pattern uses a new feature of Spring 3, namely the variable in the path. The @PathVariable annotation in front of the parameter subjectName indicates that the variable in the path needs to be bound to the method parameter.
This helps us to support any request with a given subject name and therefore we do not need to change the code if a new subject is created.
Wrong request
With the freedom of the path pattern also comes the problem that a person can try any subject name that he wants. In order to prevent that we execute a query for a subject that does not exist, we execute a query that finds the subject. If the subject is not found, we throw a NoSuchRequestHandlingMethodException. This exception is handled by the DefaultHandlerExceptionResolver. This resolver handles certain standard Spring MVC exceptions by setting a specific response status code. In our case the status code will be a 404.
protected SubjectBean findSubject(String subjectName, HttpServletRequest request, RssSubjectDao subjectDao) throws NoSuchRequestHandlingMethodException { SubjectBean subject = subjectDao.findSubjectBeanByName(subjectName); if (subject == null) { throw new NoSuchRequestHandlingMethodException(request); } return subject; }
Items
The implementation of the controller is not special. We execute a query that finds some news and with the help of a mapper, we map the retrieved beans to RSS items. These items, as well as the subject title, are added to the model.
View
Now that our model is filled, we can create the view. Spring 3 provides us with the AbstractRssFeedView. This view has a few entry points to create the feed.
We created a BaseRssFeedView which extends the AbstractRssFeedView. The BaseRssFeedView overrides three methods that build the feed.
public abstract class BaseRssFeedView extends AbstractRssFeedView { private LocalizedMessageResolver messages; @Override protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { super.prepareResponse(request, response); response.setCharacterEncoding(CharacterEncodingConstants.UTF8); } @Override protected void buildFeedMetadata(Map<String, Object> model, Channel feed, HttpServletRequest request) { RssMetaData rssMetaData = new RssMetaData(); configureRssMetaData(rssMetaData, model); feed.setTitle(rssMetaData.getFeedTitle()); feed.setDescription(rssMetaData.getFeedDescription()); feed.setLink(feedLink); feed.setWebMaster(feedWebmaster); feed.setLanguage(feedLanguage); feed.setCopyright(feedCopyright); Image image = resolveImage(rssMetaData.getFeedTitle()); feed.setImage(image); feed.setLastBuildDate(new Date()); feed.setTtl(TIME_TO_LIVE); AtomNSModule module = new AtomNSModuleImpl(); module.setLink(rssMetaData.getFeedUrl()); feed.getModules().add(module); } @Override protected List<Item> buildFeedItems(Map<String, Object> stringObjectMap, HttpServletRequest request, HttpServletResponse response) { return (List<Item>) stringObjectMap.get("items"); } protected abstract void configureRssMetaData(RssMetaData metaData, Map<String, Object> viewModel); }
I don’t want to discuss the implementation of the methods, as I think that they are pretty self-explanatory. But I do want to discuss the one abstract method that needs to be implemented by the subclasses. This method allows the subclass to specify the specific metadata for the feed. The code below shows the view that would belong to our SubjectRssController.
@Component("subjectRssFeed") public class SubjectRssFeedView extends BaseRssFeedView { @Override protected void configureRssMetaData(RssMetaData metaData, Map<String, Object> viewModel) { String subjectName = (String) viewModel.get("subjectName"); LocalizedMessageResolver messages = getMessageResolver(); metaData.setFeedUrl(messages.getMessage("subject.news.rss.feed.url", subjectName)); metaData.setFeedTitle(messages.getMessage("subject.news.rss.feed.title", subjectName)); metaData.setFeedDescription(messages.getMessage("subject.news.rss.feed.description", subjectName)); } }
The class is annotated with @Component(“subjectRssFeed”). The name of the view is subjectRssFeed and needs to match with the view name you return in the controller.
Properties
In this case we assume that every subject has the same message for the specific metadata. So the only thing that differs is the subject name. For example, the description of a feed of the subject cars could be: This feed contains the news items of the subject cars.
So this means that we need to parameterize the messages. In order to do that we have created a class that resolves the message for us from the correct property file. This way we can still use the templates in the property files.
subject.news.rss.feed.description=This feed contains the news items of the subject {0}
Spring 3 also lets you annotate a field or method/constructor parameter that indicates a default value expression for the affected argument. This means you could inject a property like this:
@Value("#{rssProperties.getProperty('subject.news.rss.feed.url')}") public void setFeedUrl(String feedUrl) { this.feedUrl = feedUrl; }
or
@Value("#{rssProperties.getProperty('subject.news.rss.feed.url')}") private String feedUrl;
Conclusion
I hope you got an idea of how Spring 3 can help you with creating RSS feeds. In our case it was really useful as we get all the advantages of Spring MVC and added new feeds are a piece of cake!