Friday, April 20, 2007

RSS Injection

Вячеслав Яковенко, Дмитрий Руденко

Ни для кого не секрет, что в последнее время RSS каналы стали не только популярным средством продвижения новостных лент, но и эффективным способом обмена информационными потоками между сайтами. В данной статье речь пойдет о том, как реализовать потребность в RSS, используя один из самых популярных фреймворков – Spring.

По умолчанию в API Spring Web MVC framework входит целый набор представлений (View), реализующих рендеринг модели в различные форматы, включая PDF и Excel. Классы, предоставляющие такую возможность, находятся в пакетах org.springframework.web.servlet.view.*. Однако готового класса, позволяющего выполнять рендеринг модели в RSS или Atom, Spring пока не предоставляет.


Одной из очень удачных библиотек, позволяющих работать с синдикат-каналами, является ROME [2], разработанная инженерами из Sun. Основным ее достоинством является то, что она предоставляет API, полностью абстрогированный от конкретных реализаций RSS и Atom форматов (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).


Как уже было сказано выше, интеграция ROME и Spring по умолчанию в данный момент отсутствует - именно решением этой задачи мы и займемся в следующих строках.


В качестве примера мы разработаем небольшое приложение (rssinj), которое по запросу к url: /rssinj/news.rss будет генерировать RSS feed для виртуальной новостной системы.

Мы начнем подготовку к разработке с объявления entry-point нашего приложения в 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>


Далее, следуя "Convention over configuration" [1], создадим rssinj-servlet.xml в котором произведем внедрение бина, позволяющего автоматически ассоциировать адрес /newsrss.rss с соответствующим контроллером:

<bean
class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
</bean>

также внедряем сам контроллер:


<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>

Как видно из кода приведенного выше, наш контроллер предполагает наличие в контексте приложения DAO-сущности, позволяющей обращаться к репозиторию и извлекать из него список новостей, к которому мы предполагаем предоставить доступ через rss-канал. Мы не будем подробно останавливаться на разработке этого слоя, и для простоты изложения, объявим список новостей непосредственно в rssinj-servlet.xml ( подробнее см. [4] ):

<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>

Таким образом, после выполненных действий в контексте приложения появится три экземпляра класса
NewsBean, содержащие title, link, publishedDate и description. Непосредственно в контроллере доступ к списку новостей осуществляется с помощью следующего вызова:

List<NewsBean> newsList = newsDao.getNewsList();

Так же следует обратить внимание читателя на то, что именно строка


<bean id="newsController" class="com.webtair.articles.rssinj.mvc.NewsRssController" >

приводит к тому, что при обработке адреса /newsrss.rss управление передается непосредственно на newsController (подробнее см. [1], 13.11.1 ). На этом подготовка среды закончена и мы можем смело приступить к разработке представления (View), позволяющего нам сформировать rss канал.

В качестве библиотеки, позволяющей формировать RSS и Atom в соответсвии со стандартами, мы остановлись на ROME ([2]), соответствующий rome-*.jar которой необходимо расположить в /lib нашего проекта. ROME использует также jDom ([3]), jar которого также понадобится в /lib (подробнее см. [4] /lib/readme.txt).

В Spring определен базовый абстрактный класс (org.springframework.web.servlet.view.AbstractView) для разработки представлений, от которого мы унаследуемся и создадим свою абстракцию (com.webtair.articles.rssinj.view.RssAbstractView) для дальнейшей работы с rss:

Ключевым, в нашем абстрактном классе является метод:

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());
}

Данный метод создает экземляр SyndFeed - базовый класс в ROME, используемый для создания нового канала и затем передает управление в метод buildRssDocument(), который выполнит наполнение объекта feed, конкретными данными. Естественно, что эту функцию будет выполнять уже конкретная реализация - в нашим примере - это com.webtair.articles.rssinj.view.NewsRssView, к рассмотрению которого мы и переходим. Переопределенный метод
buildRssDocument как раз и предназанчен для того, чтобы сформировать из полученной модели полноценный feed :

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);
}

Для завершения задачи нам осталось только разработать контроллер, который выполнит обращение к репозиторию и передаст сформированную модель (Model) в представление (View). За основу мы возьмем org.springframework.web.servlet.mvc.AbstractCommandController и создадим наш
com.webtair.articles.rssinj.mvc.NewsRssController, метод handle которого необходимо переопределить:

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);
}

В нашей реализации метод получает список новостей (newsList), вкладывает его в модель (model) и передает во вновь созданный экземляр NewsRssView.

Теперь осталось только собрать и развернуть проект в servlet-контейнер. Предполагая, что имя проекта будет называться rssinj, запускаем браузер: http://localhost:8080/rssinj/newsrss.rss



и наблюдаем корректно сформированный RSS.

На последок:
В качестве дополнительных "изысков" можно добавить в контроллер обработку ожидаемого типа потока (
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), передать его через модель в наше представление (NewsRssView) и выставить соответствующий feedType:

feed.setFeedType( feedType );

что позовлит предоставить пользователям выбор между синдикат-каналами.


Ссылки:

  1. The Spring Framework - Reference Documentation v2.0.2 ( http://www.springframework.org/docs/reference/index.html );
  2. ROME is an set of open source Java tools for parsing, generating and publishing RSS and Atom feeds ( https://rome.dev.java.net );
  3. jDom ( http://www.jdom.org );
  4. Исходные коды примера доступны по адресу: http://www.webtair.com/source/rssinj.zip.

Список, необходимых библиотек:
  1. spring.jar ( v.2.0.2 from $SPRING_HOME/dist/ )
  2. rome.jar ( v.0.9 from https://rome.dev.java.net/ )
  3. jdom.jar ( v.1.0 from http://www.jdom.org/dist/binary/ )
  4. commons-logging.jar ( $SPRING_HOME/dist/jakarta-commons/ )

Friday, December 29, 2006

SessionMan gives a solution to the problem of saving state while using web-services

Review

Web-services have become so popular long ago, therefore their support is now included in Java SE 6. Now it’s hard to surprise someone with the layered architecture, consisting of Java server side and .Net clients side or vice versa. Web-services gives a lot of advantages for such kind of a challenge, but, nevertheless, they have a lot of disadvantages. The main goal of this article is to provide you with a good solution for one of them.


The problem

"By design, Web services are said to be stateless..." – this statement says a lot. It means that you will have no problems passing the code of a city to the server to retrieve weather forecast for the next week. But to organize a client-bank system with the usage of Axis2 you’ll have to work for a while, because such systems assumes authorization procedure and supporting of a session, created for a user, for some time period.


*Axis2 – second generation of the most popular web-services engine, developed under the aegis of Apache Software Foundation.

Possible solutions of the problem under the Axis2 platform

The search of the solution to this problem has leaded me to some article [1], where IBM folks proposed their own solution. To make the long story short, they have proposed to add to the service-class, generated by Axis2 a static HashMap field, which would behave as a storage of the users’ session information. And WSDL services would contain two additional operations: login and logout. Everything seems to work fine, though there is one small problem, or, we should rather call it a “restriction”. The user should explicitly call the “logout” method, otherwise information of the session while be alive till the next server reload and the HashMap will be the source of memory leaks and performance problems. We propose you a solution to this restriction: open source SessionMan library, providing you with the basic functionality to work with sessions in any Java-based server application. It provides you with the comfortable api to work with sessions, including the periodic garbage collection (which consists in cleaning the expired sessions).


To run the SessionMan usage example, you should have TomCat and Axis2 running and working. It it’s needed, you may install TomCat and Axis2 using their installation notes and we may continue.

WSDL – core part of any web-service

The first step of our job is to create a wsdl-document, describing our future web-sercice:



As we can see from this wsdl fragment, our web-service will consist of three operations: login, disconnect** and ping.

First operation will have SOAP envelop, wrapping user’s login and password, as the input data.


**method’s name was chosen not accidentally, as in one of previous Axis2 v.1.0 versions there were some bus with usage the “logout” name for a method.



The latter two receive SOAP envelop, wrapping only session id, which is on one hand needed to check the session’s validity before executing any operation, on the other hand, it is quite enough to for the SessionMan’s example.


After the WSDL-document is created, we laungh the wsdl2java tool, which is a part of Axis2 and what until it generates all the classes needed.


Fully functional example of working with SessionMan library jointly with Axis2 could be downloaded from the following svn repository:

http://sessionman.googlecode.com/svn/trunk/sessionman-example.


Instruction of working and building the product for Eclipse 3.2 environment could be found here: http://sessionman.googlecode.com/svn/trunk/sessionman-example/readme.txt.

SessionMan comes

wsdl2java will generate a whole package of classes, one of which will have the following name SessionManExampleServiceSkeleton – a heart of our future service. For the future work we should inherit this class and create our own SessionManExampleService.


Connecting SessionMan:


import com.webtair.session.*;

import com.webtair.session.artifact.*;

import com.webtair.sessionman.ws.types.*;


Adding our sessions handler to the class:


public class SessionManExampleService extends

SessionManExampleServiceSkeleton {


...

/** SessionMan instance — main goal of this example */

private static SessionMan sessions

= new SessionMan();


Some clarifications are needed there:


  1. Class com.webtair.session.SessionMan has two constructors: public SessionMan(ConfigBean config) and public SessionMan() — the latter creates an instance of the SessionMan with the default configuration (session’s lifetime is 30 min and time period to clean expired sessions is 10 min). The former constructor allows the user to provide SessionMan with his/her own configuration. To see full configuration properties please see com.webtair.session.config.ConfigBean

  2. Any class, which implements com.webtair.session.artifact.SessionInfo interface could be used as the storage of session’s inforation, SimpleSessionInfo is the default realization of this interface, it contains only basic operations.


Thus, if you need to create a storage for, say, the state of a user’s basket, you could simply create some realization of SessionInfo interface with such functionality and use it in SessionMan. So, we create a session and return it to the web-service’ client.


/**

* Method that allow for client to login under this ws

* @return IdSessionDocument — soap envelope with session id;

*/

@Override

public IdSessionDocument login (UserAuthElementDocument userAuthElementDocument) {

/* default login and pass for examples only. At this place you

* mast recieve real pass from the real storage like RDBMS or

* config-file or somthing else.

*/

String defaultLogin = "sessionman";

String defaultpass = "example";


UserAuthElement userAuthElement =

userAuthElementDocument.getUserAuthElement();


if (userAuthElement.getLogin().equals(defaultLogin)

&& userAuthElement.getPassword().equals(defaultpass)){


// request for id session from SessionMan lib

idSession = sessions.putSessionInfo(userAuthElement.getLogin(),

new SimpleSessionInfo());

}

...

}


The crucial part of retrieving the session’s id is putSessionInfo() method, which puts the SessionInfo controller to the HashMap and returns the unique session’s id.


Checking if session is alive:


  • validateSession(String idSession) method allows not only to check if the session is expired or not, but it also refreshes its last usage time, automatically prolonging its life time for the value, stated in the ConfigBean (30 min is the default one).
  • Calling isSessionExpired(String idSession) method simply returns the information if the session is alive, without any refreshing.

LogOut and sessions cleaning

If the user has not done the explicit disconnect(logout) (via calling the corresponding method), the session will be killed by the background thread, sleeping for the time interval, stated in ConfigBean and then locking and cleaning the HashMap.


Concluding remarks

Fully functional example of working with SessionMan library jointly with Axis2 could be downloaded from the following svn repository:

http://sessionman.googlecode.com/svn/trunk/sessionman-example.


Resources

1. Online banking with Apache Geronimo and Axis2: http://www-128.ibm.com/developerworks/edu/os-dw-os-ag-onbank1.html;

2. WebTair SessionMan library: http://www.webtair.com/sessionman;

3. Example for SessionMan and Axis2:http://sessionman.googlecode.com/svn/trunk/sessionman-example;

4. SessionMan online API JavaDoc: http://www.webtair.com/sessionman/api/.



Vyacheslav Yakovenko, Dmitry Rudenko,

webtair.com

Sunday, December 24, 2006

Session Man for Axis2 example

Example that illustrates collaboration between SessionMan and Axis2 is released. Please, checkout it from the Google Code SVN repository: http://sessionman.googlecode.com/svn/trunk/sessionman-example.

In this example you'll see how easily can you manage with sessions in web services based on Axis2 distribution using WebTair's SessionMan library.

Wednesday, December 20, 2006

SessionMan library v.0.1.0 relesed

We proud to announce the v.0.1.0 of our SessionMan library.

You can download it from http://sessionman.googlecode.com/files/webtair-sessionman-0.1.0.jar