Web Services and Axis2

While web services are supported on many platforms, I like to use Axis2 for a couple of reasons:

So why would you want a stand-alone server when your webserver running on port 80 could host web services? For me it's a matter of control. By configuring your firewall appropriately you can control who has external access to the web services server. Whether it's a hardware firewall such as a Cisco or a software solution like iptables, you obviate the need to use security tokens or some other software access control mechanism. It's just a different approach.

The Axis2 server requires a particular file format (Axis archive or aar) for automatic deployment. While it's certainly possible to generate the archive through manual or semi-manual processes, I prefer to use the capabilities provided by Axis2 and documented below. My preferred approach involves first creating a "skeleton" containing the methods I want to expose. Here's an example:

package	net.sudsy.AuthenticationService;

public class SimpleClass {
	public String authenticate( String ccnum ) {
		return( "true" );
	}
}

Compile the source and generate the class structure relative to the desired destination.

javac -d destinationDirectory SimpleClass.java

Now we can use the java2wsdl class to generate the corresponding WSDL. I like to use a shell script to invoke java2wsdl since it takes away a lot of the headaches. I should note that, on my system, Axis2 resides in the /opt2/axis2 directory. Here's the script:

#!/bin/bash
export AXIS2_HOME=/opt2/axis2
export AXIS2_LIB=$AXIS2_HOME/lib
export CLASSPATH
for filename in $AXIS2_LIB/*.jar
do
	if [ -z "$CLASSPATH" ]
	then
		CLASSPATH=$filename
	else
		CLASSPATH=$CLASSPATH:$filename
	fi
done
export USER_ARGS=
while [ $# -gt 0 ]
do
	if [ x$1 = x-cp ]
	then
		shift
		CLASSPATH=$CLASSPATH:$1
	else
		USER_ARGS="$USER_ARGS $1"
	fi
	shift
done
java org.apache.ws.java2wsdl.Java2WSDL $USER_ARGS

Here's an invocation using the SampleClass I wrote and compiled earlier:

java2wsdl -cp destinationDirectory -cn net.sudsy.AuthenticationService.SimpleClass -sn AuthenticationService
          -tn http://www.sudsy.net/axis2 -stn http://www.sudsy.net/axis2/xsd

Without going into details, we specify the class name, service name and some namespaces. You'll need to refer to the documentation in order to fully understand the arguments. Here's the output WSDL file:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://www.sudsy.net/axis2"
    xmlns:axis2="http://www.sudsy.net/axis2"
    xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
    xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
    xmlns:ns="http://www.sudsy.net/axis2/xsd"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
    <wsdl:types>
        <xs:schema attributeFormDefault="qualified"
            elementFormDefault="qualified"
            targetNamespace="http://www.sudsy.net/axis2/xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
            <xs:element name="authenticate">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="param0" nillable="true" type="xs:string"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="authenticateResponse">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="return" nillable="true" type="xs:string"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="authenticateMessage">
        <wsdl:part element="ns:authenticate" name="part1"/>
    </wsdl:message>
    <wsdl:message name="authenticateResponseMessage">
        <wsdl:part element="ns:authenticateResponse" name="part1"/>
    </wsdl:message>
    <wsdl:portType name="SimpleClassPortType">
        <wsdl:operation name="authenticate">
            <wsdl:input message="axis2:authenticateMessage"/>
            <wsdl:output message="axis2:authenticateResponseMessage"/>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="SimpleClassSOAP11Binding" type="axis2:SimpleClassPortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="authenticate">
            <soap:operation soapAction="urn:authenticate" style="document"/>
            <wsdl:input>
                <soap:body namespace="http://www.sudsy.net/axis2" use="literal"/>
            </wsdl:input>
            <wsdl:output>
                <soap:body namespace="http://www.sudsy.net/axis2" use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:binding name="SimpleClassSOAP12Binding" type="axis2:SimpleClassPortType">
        <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="authenticate">
            <soap12:operation soapAction="urn:authenticate" style="document"/>
            <wsdl:input>
                <soap12:body namespace="http://www.sudsy.net/axis2" use="literal"/>
            </wsdl:input>
            <wsdl:output>
                <soap12:body namespace="http://www.sudsy.net/axis2" use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="SimpleClass">
        <wsdl:port binding="axis2:SimpleClassSOAP11Binding" name="SimpleClassSOAP11port">
            <soap:address location="http://localhost:8080/axis2/services/SimpleClass"/>
        </wsdl:port>
        <wsdl:port binding="axis2:SimpleClassSOAP12Binding" name="SimpleClassSOAP12port">
            <soap12:address location="http://localhost:8080/axis2/services/SimpleClass"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

I should note that I've run the generated WSDL through a "pretty" processor in order to make it easier to read. While XML is plain text and is supposed to be human-readable, there's no requirement for line breaks.

Now it's very important to change the port bindings manually to match up to the actual deployment environment. Replace the hostname and port in the location URL to match. Since I run the axis2 server locally on port 7080 I would change this section:

    <wsdl:service name="SimpleClass">
        <wsdl:port binding="axis2:SimpleClassSOAP11Binding" name="SimpleClassSOAP11port">
            <soap:address location="http://localhost:8080/axis2/services/SimpleClass"/>
        </wsdl:port>
        <wsdl:port binding="axis2:SimpleClassSOAP12Binding" name="SimpleClassSOAP12port">
            <soap12:address location="http://localhost:8080/axis2/services/SimpleClass"/>
        </wsdl:port>
    </wsdl:service>

to this:

    <wsdl:service name="SimpleClass">
        <wsdl:port binding="axis2:SimpleClassSOAP11Binding" name="SimpleClassSOAP11port">
            <soap:address location="http://localhost:7080/axis2/services/SimpleClass"/>
        </wsdl:port>
        <wsdl:port binding="axis2:SimpleClassSOAP12Binding" name="SimpleClassSOAP12port">
            <soap12:address location="http://localhost:7080/axis2/services/SimpleClass"/>
        </wsdl:port>
    </wsdl:service>

Next I run the WSDL through wsdl2java. As with java2wsdl, I like to use a shell script to invoke wsdl2java. It's almost exactly the same as the other one:

#!/bin/bash
export AXIS2_HOME=/opt2/axis2
export AXIS2_LIB=$AXIS2_HOME/lib
export CLASSPATH
for filename in $AXIS2_LIB/*.jar
do
	if [ -z "$CLASSPATH" ]
	then
		CLASSPATH=$filename
	else
		CLASSPATH=$CLASSPATH:$filename
	fi
done
export USER_ARGS=
while [ $# -gt 0 ]
do
	if [ x$1 = x-cp ]
	then
		shift
		CLASSPATH=$CLASSPATH:$1
	else
		USER_ARGS="$USER_ARGS $1"
	fi
	shift
done
java org.apache.axis2.wsdl.WSDL2Java $USER_ARGS

Here's the invocation of the command:

wsdl2java -ss -sd -o destinationDirectory -uri pathToWSDLFile

The outputDir doesn't have to exist; it will be created automatically. In fact, I prefer that the output directory not already exist so that I can have a pristine platform for development. The pathToWSDL should be obvious; it's the path to the file which was generated by java2wsdl.

The output directory will contain two directories (src and resources) and a build.xml file. In this example, the skeleton will be located in the src/net/sudsy/www/axis2 directory and named SimpleClassSkeleton.java. Now it's simply a matter of "fleshing out" the exposed method(s) and invoking ant at the top level of the directory tree. The output archive file will be located in the build/lib subdirectory and, in this case, will be named SimpleClass.aar. Deployment is achieved by copying this file to the ${AXIS2_HOME}/repository/services directory. It will be automagically deployed.

Creating the client is even easier. Hint: remove the -ss (server side) and -sd (service descriptor) flags from the invocation of wsdl2java.

Copyright © 2009, 2010, 2011, 2012, 2013, 2014, 2015 by Phil Selby. All rights reserved.