Monday, June 13, 2011

XML-RPC + Maven + Spring

I was lately trying to implement a basic set of services in XML-RPC. Well, it had to be XML-RPC as a architectural design constraint if you are interested why . Other than that I had other requirements as well: It had to be on Spring, must be built by Maven, and most importantly it had to be a WAR file in a container not a standalone application.

Googling around, I came up with a few blog posts providing hints and code snippets. Here are some of them that I actually based my code on:
Salmon Run (07 Feb 2010)
Tomas Salfischberger (28 March 2008)
Perry Nguyen (6 Oct 2006)
Here I am not adding much to what these guys have already said, but instead doing what they could have done to save me hours of trial and error: Providing a complete working example.

The sample application provided here is a Spring based web application that can be hosted in an application server like Tomcat.

You can browse the code in this SVN repository. Alternatively you might try this command to check it out completely from repository:
svn checkout http://tinywebgears-samples.googlecode.com/svn/trunk/xmlrpcsample/ xmlrpcsample

This application also provides a sample Java client that can be used to call the sample service. The client code connects to the server at this pre-configured location:
http://127.0.0.1:8080/xmlrpc-backend-services/xmlrpc/
and then invoke the sample service called 'sampleService.getVersion'.
You might need to change the client code if you deploy in a different configuration.

For those who are intersted, I have also been able to add another view controller and other set of Spring MVC views to serve a web application living besides the XML-RPC services, but I removed those bits in order to keep it simple.

The parts of configuration that tend to go wrong (and took me bloody hours to get them fixed) are web.xml and Spring's beans configuration file. Here is my web.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/META-INF/properties/log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

<!-- Spring context -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:WEB-INF/spring/*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Spring webapp -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!-- Welcome file -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

</web-app>



And finally the beans configuration will be as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">


<!-- XML-RPC -->

<!-- XML-RPC Services -->
<bean id="sampleService" class="com.tinywebgears.samples.service.SampleServiceImpl">
</bean>
<!-- Processor Factory -->
<bean id="requestProcessorFactory"
class="com.tinywebgears.samples.xmlrpc.ConfiguredBeanProcessorFactory">
<property name="classBeanMap">
<map>
<entry key="com.tinywebgears.samples.service.SampleService"
value-ref="sampleService" />
</map>
</property>
</bean>
<!-- Controller -->
<bean id="xmlRpcHandler" class="com.tinywebgears.samples.xmlrpc.XmlRpcServerController">
<property name="factory" ref="requestProcessorFactory" />
<property name="mappings">
<props>
<prop key="sampleService">com.tinywebgears.samples.service.SampleService</prop>
</props>
</property>
</bean>


<!-- Dispatcher -->

<bean id="simpleUrlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">
<props>
<prop key="/xmlrpc/*">xmlRpcHandler</prop>
</props>
</property>

</bean>

<context:component-scan base-package="com.tinywebgears.samples" />

</beans>