Thursday, September 15, 2005

How to publish an Axis webservice in Tomcat using Spring

I thought as noone has produced a good tutorial on how to combine axis and spring I would document my experiences as I try to get both working.

First things first: I've installed Axis v1.2.1 from the download site and set it up in my Tomcat 5.0.28 environment. HappyAxis.jsp is reporting all good. Setting these two fellas up is well documented on the respective apache sites so I'll start my commentary at the point of setting up new service.

Pre-requisites:

0. Add all of the below jars (or newer versions) to your WEB-INF/lib dir:

07/05/2005  21:14            55,147 activation.jar (from Sun, Java Activation Framework) 
14/09/2005 15:01 7,556 my-code.jar (this is where my app stuff lives)
14/06/2005 22:28 33,506 axis-ant.jar (Axis stuff)
14/06/2005 22:44 1,604,162 axis.jar (Axis stuff)
17/08/2005 17:36 188,671 commons-beanutils-1.7.0.jar (Spring stuff)
19/08/2005 15:51 518,641 commons-collections-3.0.jar (Spring stuff)
19/08/2005 15:51 139,966 commons-digester-1.7.jar (Spring stuff)
14/06/2005 22:28 71,442 commons-discovery-0.2.jar (Spring stuff)
17/08/2005 17:24 169,763 commons-lang-2.0.jar (Spring stuff)
14/06/2005 22:28 38,015 commons-logging-1.0.4.jar(Spring stuff)
14/06/2005 22:28 32,071 jaxrpc.jar (I got this from Suns Webservices ToolkitSuns Webservices toolkit, it was a pain to find)
14/06/2005 22:28 352,668 log4j-1.2.8.jar (Axis stuff)
09/09/2005 15:28 355,030 mail.jar (Sun mail jar)
14/06/2005 22:28 19,427 saaj.jar (Again part of this distro Suns Webservices ToolkitSuns Webservices toolkit, it was a pain to find)
07/09/2005 17:38 1,847,568 spring-1.2.4.jar (Dear friend Spring)
14/06/2005 22:28 126,771 wsdl4j-1.5.1.jar (I think this came with Axis but I may be wrong)


Steps for creating service:

1. Create an end-point for your service by extending the Spring class: ServletEndpointSupport

This was mine:

package com.drkw.cpds.webservices.axis.sample;

import java.rmi.RemoteException;

import org.springframework.context.ApplicationContext;
import org.springframework.remoting.jaxrpc.ServletEndpointSupport;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class SampleAtom2ServiceServletEndPoint extends ServletEndpointSupport {

private ISampleAtom2Service _service = null;

public SampleAtom2ServiceServletEndPoint(){

WebApplicationContext ctx = this.getWebApplicationContext();
_service = (ISampleAtom2Service)ctx.getBean("sampleServiceImpl");
}

public Obligor[] getObligors() throws RemoteException {
return _service.getObligors();
}

public Obligor getObligor() throws RemoteException {
return _service.getObligor();
}

public User getUser() throws RemoteException {
return _service.getUser();
}

public User[] getUsers() throws RemoteException {
return _service.getUsers();
}

public void throwMeAnExecption() throws RemoteException {
try {
_service.throwMeAnException();
} catch(Exception ex){
throw new RemoteException( ex.getMessage(), ex );
}
}
}



As you can see I delegate everything to a real business implementation. As you can see from the code, this is my starting point to spring where I load my WebApplicationContext (this is analogous to the ApplicationContext interface except that it is keyed in the application store for you).

2. Add the spring listener configuration to your web.xml

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>



3. Create an applicationContext.xml in the WEB-INF/ dir of your web app. It seems that Spring loads any file called application context by default. You can specify which files you want spring to load from a tag inside the web.xml but I have yet to venture this. Mine was exactly like this:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="sampleServiceImpl" class="com.drkw.cpds.webservices.axis.sample.SampleAtom2Service" singleton="true"/>
</beans>


4. Define your service in the server-config.wsdd in the axis webapp dir in tomcat, like so:
(please note that this can be done using the admin client but I couldn't get that to work in this case so altered the file directly)

 <service name="Atom2SampleService" provider="java:RPC">
<requestFlow>
<handler type="soapmonitor"/>
</requestFlow>
<responseFlow>
<handler type="soapmonitor"/>
</responseFlow>
<parameter name="allowedMethods" value="*"/>
<parameter name="scope" value="Application"/>
<parameter name="className" value="com.drkw.cpds.webservices.axis.sample.SampleAtom2Service"/>
<!-- these are required if you have any POJO's you want to return above the standard primitives and java objects (String, Integer etc) -->
<beanMapping qname="myNS:Obligor" xmlns:myNS="urn:BeanService" languageSpecificType="java:com.drkw.cpds.webservices.axis.sample.Obligor"/>
<beanMapping qname="myNS:User" xmlns:myNS="urn:BeanService" languageSpecificType="java:com.drkw.cpds.webservices.axis.sample.User"/>

</service>


nb. If you have not set up the soapmonitor service/applet as described in the axis documentation then you will need remove the requestFlow and responseFlow elements.

5. Well that's it really. Fire-up your tomcat instance and check that all is well in the location of your web-app I found it invaluble to check the logs at this time. If all is well you should have something like this in them:

stdout.log

  INFO: Installing web application at context path /atom2 from URL file:C:\Program Files\Apache Software Foundation\Tomcat 5.0\webapps\atom2
- Root WebApplicationContext: initialization started
- Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
- Bean factory for application context [Root WebApplicationContext]: org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [sampleServiceImpl]; root of BeanFactory hierarchy
- 1 beans defined in application context [Root WebApplicationContext]
- JDK 1.4+ collections available
- Commons Collections 3.x available
- Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@190ef12]
- Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@32bd65]
- Unable to locate ThemeSource with name 'themeSource': using default [org.springframework.ui.context.support.ResourceBundleThemeSource@5a3923]
- Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [sampleServiceImpl]; root of BeanFactory hierarchy]
- Creating shared instance of singleton bean 'sampleServiceImpl'
- Using context class [org.springframework.web.context.support.XmlWebApplicationContext] for root WebApplicationContext
- Root WebApplicationContext: initialization completed in 297 ms




localhost_((date)).log
2005-09-14 16:05:00 StandardContext[/atom2]Loading Spring root WebApplicationContext


After this I went on to test my service using C# (as this will be our main client) and it all ran like a dream. I've heard that there are many problems with RPC style service and dotnet but as yet (touches wood, hangs horseshoes up, prays west to Mecca) I haven't experienced this.

If anyone has any other comments configuration info to add, please post as comments.

I'm hoping my next post will be an example of how to consume my service with Spring and Axis......

Chris