I recently started searching for a SOAP framework and took a look at Axis2. Actually, I looked at it twice. The first time I passed because the deployment model was too confusing. Only after evaluating the downsides of the other frameworks did I take a second look. I eventually went with it after I figured out how to get it to meet my two main requirements:

  1. A simple coding and deployment model: I didn’t something so complex that a wizard has to hide the complexity from me. I especially didn’t want a hot deployment of a special archive into a magic axis webapp. Just give me a simple configuration file to edit or some annotations for my service classes.
  2. IoC container integration. Instead of directly instantiating a service class and calling a method, I wanted Axis2 to invoke a method on one of my services defined in my IoC container (Spring, in this case).

I put together a sample project which demonstrates a simple approach to Axis2. The application exposes a single web service for retrieving products from a catalog. The service has two methods: getAll, and getForId.

Getting and Running the Project

The sample project can be found at http://bentomasini.com/svn/repos/demos/catalog/trunk. To try it out, checkout the source and start the application by doing the following. Maven2 is required.

svn co http://bentomasini.com/svn/repos/demos/catalog/trunk catalog
cd catalog
mvn jetty:run

Now open http://localhost:8080/catalog. From there you can open the WSDL and run a few operations using the quasi-RESTful Axis2 interface. To call the service using SOAP, or to generate clients for various SOAP frameworks, I recommend using soapUI. Just create a new WSDL project using the WSDL URL linked from the home page.

Java Code and Spring Configuration

The project’s service class, shown below, calls the injected dao and converts List<Product> to Product[]. SOAP frameworks choke on collections.

package com.bentomasini.catalog;

import com.bentomasini.catalog.Product;
import com.bentomasini.catalog.dao.ProductDao;

/**
*
* @author btomasini
* @since Jul 15, 2007
*
*/

public class ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public Product[] getAll() {
        return productDao.getAll().toArray(new Product[0]);
    }

    public Product getForId(Long id) {
        return productDao.getForId(id);
    }

}

The applicationContext.xml located in WEB-INF wires up the Spring bean and its dao:

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

  <bean id="productDao" class="com.bentomasini.catalog.dao.impl.ProductDaoImpl"/>

  <bean id="productService" class="com.bentomasini.catalog.ProductService">
    <property name="productDao" ref="productDao" />
  </bean>

</beans>

Axis2 Configuration and web.xml

The method I used to configure Axis2 is key to keeping the project simple. It also requires some background explanation. The Axis2 folks have a concept that developers should package their web services in a new type of archive, an aar, and deploy that aar into a canned Axis2 web application. This makes me scratch my head and go “huh?” I don’t think most developers want to do this. I know I don’t. I simply want to build a web application, specify some services, and package my application in one single application archive - a war.

The good news is that developers don’t have to create aar archive files. Taking a look at the aar deployment model, we see that these service archives are deployed into WEB-INF/services. Alternatively, service archives can be exploded into a directory by the same base name. So WEB-INF/services/ProjectService.aar can instead by deployed by exploding its contents into the directory WEB-INF/services/ProjectService. The magic file in the archive is META-INF/services.xml. It defines the services to be deployed by Axis2. This file can contain a single service definition, or multiple service definitions inside of a service-group element.

We can do away with all of this hoo-ha by having a single services.xml for our entire project. Just pick a generic name for the directory, say “WebService”. Now we can put a single file in our project to define services: WEB-INF/services/WebService/META-INF/services.xml. Just put all of your service definitions in there and enjoy the simplicity. Here is the services.xml file from the sample project:

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

  <service name="ProductService">
    <parameter name="ServiceObjectSupplier" locked="false">
      org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectSupplier
    </parameter>
    <parameter name="SpringBeanName" locked="false">productService</parameter>
    <operation name="getForId">
      <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
    </operation>
    <operation name="getAll">
      <messageReceiver class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
    </operation>
    <excludeOperations>
      <operation>setProductDao</operation>
    </excludeOperations>
  </service>

  <!-- Add your second service here -->

</serviceGroup>

The service element configures our service for deployment and specifies a special ServiceObjectProvider which knows how to obtain the service from our Spring context. The name of the Spring bean is also provided. Each operation has an operation element and text marking it as an RPC method. In addition, our setProductDao method is excluded because it is only meant for use by our IoC container for dependency injection. Adding another operation or service is simply a cut-and-paste exercise.

The web.xml to load both Spring and Axis2:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>AxisServlet</servlet-name>
    <servlet-class>org.apache.axis2.transport.http.AxisServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>AxisServlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
</web-app>

That’s it!