|
|
RSS InjectionIntroductionIt's quite well-known that RSS channels have become not only the popular facility to spread news but also an effective way to exhange feeds of information between web-sites. This article's main purpose is to clarify how to implement the RSS via usage of one of the most popular web-framework - Spring Framework. Spade-workSpring Web MVC framework API contains a whole bunch of default representations (Views), representing model rendering to various formats, even including PDF and MS Excel. Classes with the corresponding abilities are contained in packages org.springframework.web.servlet.view.*. However, no class to provide us with the API to render model to RSS or Atom is in the current (2.0.4) version of Spring. ROME [2], provided by Sun corporation, has proved itself to be one of the most firm libraries to work with syndicate-channels. Its main advantage is that it provides the abstract facade API, which supports any kind of RSS and Atom feeds (RSS 0.90, RSS 0.91 Netscape, RSS 0.91 Userland, RSS 0.92, RSS 0.93, RSS 0.94, RSS 1.0, RSS 2.0, Atom 0.3, and Atom 1.0). So our task is to integrate ROME with Spring, and we'll prove this is possible. As an example of this we'll develop a small application (rssinj), which will generate RSS feeds for virtual news system having received the following url - url: /rssinj/newsrss.rss Our spade-work starts from declarating an entry-point for our application in web.xml:
<servlet>
<servlet-name>rssinj</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>rssinj</servlet-name>
<url-pattern>*.rss</url-pattern>
</servlet-mapping>
Further, following the "Convention over configuration" [1], we're creating rssinj-servlet.xml where we declare bean injection, allowing to associated /newsrss.rss URL with the corresponding controller:
<bean
class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
</bean>
following this by injecting the controller itself:
<bean id="newsController"
class="com.webtair.articles.rssinj.mvc.NewsRssController" >
<property name="commandClass" value="java.lang.Object" />
<property name="newsDao">
<ref bean="newsDao" />
</property>
</bean>
As we can see from the source-code, written above, our controller supposes that a DAO-entity exists somewhere in the application context, which allows to get the list of news to be transformed to RSS feed from some repository. This article is not aimed to cover the DAO layer, and, for the purpose of simplicity, we will declare the list of news right in the rssinj-servlet.xml ( see [4] for details ):
<bean id="newsDao" class="com.webtair.articles.rssinj.dao.NewsDao">
<property name="newsList">
<list>
<ref bean="news1"/>
<ref bean="news2"/>
<ref bean="news3"/>
</list>
</property>
</bean>
<bean id="news1" class="com.webtair.articles.rssinj.domain.NewsBean">
<property name="title" value="Adobe unveils Flash video control" />
<property name="link" value="http://news.bbc.co.uk/2/hi/business/6558979.stm" />
<property name="publishedDate" value="2007-04-16" />
<property name="description" value="Adobe unveils a..." />
</bean>
<bean id="news2" class="com.webtair.articles.rssinj.domain.NewsBean">
...
</bean>
<bean id="news3" class="com.webtair.articles.rssinj.domain.NewsBean">
...
</bean>
Thus, having done all afore-written we'll have three instances of NewsBean class, which contains title, link, publishedDate and description fields. Access to the list of news in the controller itself is done through the following call:
List<NewsBean> newsList = newsDao.getNewsList();
Also it's quite important to draw attention on the following string
<bean id="newsController" class="com.webtair.articles.rssinj.mvc.NewsRssController" >
That's why during the processing of /newsrss.rss url control over the url goes directly to newsController (see [1], 13.11.1) Now we've done all the preparations and we can start working on the View, or, otherwise speaking, form the RSS channel. DevelopmentAs a RSS and Atom creating library, we have chosen ROME ([2]), so the corresponding rome-*.jar should be placed to the /lib directory of our project. ROME depends on the jDom third-party library ([3]), thus the corresponding jdom.jar should also be placed in the /lib folder. (see [4] /lib/readme.txt) There is a base abstract class in Spring (org.springframework.web.servlet.view.AbstractView) to develop views. We inherit from it and create our own abstraction (com.webtair.articles.rssinj.view.RssAbstractView) for further RSS work: The crucial method in our abstract class is:
protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
SyndFeed feed = new SyndFeedImpl();
buildRssDocument(model, feed, request, response);
response.setContentType("application/xml; charset=UTF-8");
SyndFeedOutput out = new SyndFeedOutput();
out.output(feed, response.getWriter());
}
This method creates an instance of SyndFeed class - basic class in ROME, which is used to create new channel and to pass control to bulidRssDocument() method which will fulfill the "feed" object with the data specified. Of course, this method would be implemented by scecific implementation of our abstract class - in our example it is com.webtair.articles.rssinj.view.NewsRssView, which will be described next. Overridden method buildRssDocument is creating an RSS feed from the model received:
protected void buildRssDocument(Map model, SyndFeed feed,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
feed.setFeedType( "rss_2.0" );
feed.setTitle("News Feed");
feed.setLink("http://localhost:8080/rssinj/newsrss.rss");
feed.setDescription("All today's news feed");
List<SyndEntry> entries = new ArrayList<SyndEntry>();
SyndEntry entry;
SyndContent description;
Map modelMap = (Map)model.get(Constants.MODEL);
if (modelMap != null) {
List<NewsBean> newsList =
(List<NewsBean>) modelMap.get(Constants.MODEL_NEWS_LIST);
if (newsList != null) {
for (NewsBean news : newsList) {
if (news != null) {
entry = new SyndEntryImpl();
entry.setTitle(news.getTitle());
entry.setLink(news.getLink());
entry.setPublishedDate(
DATE_PARSER.parse(news.getPublishedDate()));
description = new SyndContentImpl();
description.setType("text/html");
description.setValue(news.getDescription());
entry.setDescription(description);
entries.add(entry);
} else {
logger.info("news is null in the model");
}
}// for
} else {
logger.info(Constants.MODEL_NEWS_LIST
+ " not found in the model");
}//if/else
} else {
logger.info(Constants.MODEL + "is null");
}//if/else
feed.setEntries(entries);
}
To finish our task we have to create a controller, which will make a call to repository and pass the Model received to the View specified. We take org.springframework.web.servlet.mvc.AbstractCommandController as a basis and create our com.webtair.articles.rssinj.mvc.NewsRssController, method handle() of which we have to override:
protected ModelAndView handle(HttpServletRequest req,
HttpServletResponse res, Object command, BindException error)
throws Exception {
Map<String, Object> model = new HashMap<String, Object>();
List<NewsBean> newsList = newsDao.getNewsList();
model.put(Constants.MODEL_NEWS_LIST, newsList);
NewsRssView view = new NewsRssView();
return new ModelAndView(view, Constants.MODEL, model);
}
Our implementation of this method receives the list of news (newsList), puts it to the model and passes to newly created instance of NewsRssView class. The only thing wich is left is to build the project and deploy it to a servlet-container. Assuming the project name would be rssinj, we open url http://localhost:8080/rssinj/newsrss.rss in a browser. ![]() and we can see the well-formed RSS. Final notes:As an additional exquisite we could add processing of the expected RSS feed type (RSS 0.90, RSS 0.91 Netscape, RSS 0.91 Userland, RSS 0.92, RSS 0.93, RSS 0.94, RSS 1.0, RSS 2.0, Atom 0.3, and Atom 1.0), pass it through model to our view (NewsRssView) and set the corresponding feedType:
feed.setFeedType( feedType );
which will allow the user to choose between the syndicate-channels. Links
Libraries used:
Vyacheslav Yakovenko, Dmitry Rudenko, |