Monday, 28 January 2013

Axis2 Web Service Client Tutorial

Introduction
Axis2 is a Java framework that provides comprehensive support for exposing and consuming web services. This short post will look at its SOAP client support and how it can be used to get a simple web service client up and running.
For convenience I'm going to be calling a web service that I recently built as part of another blog post.  If you don't already have a web service to call you can grab the full source code for my sample service from github. Simply run a Maven build and deploy the WAR to your Servlet container.

What is a Web Service Client
This post doesn't attempt to explain the detailed inner workings of a web service client, but its still pretty useful to have an idea of what's going on under the hood. Most web service clients provide the following
  • A client side proxy for the remote service we want to call, that allows our application to invoke a SOAP service using a simple method call. The proxy insulates our application from the intricacies of sending and receiving SOAP messages.
  • Marshalling of Java objects to XML so that application data can be converted to SOAP payloads for posting to the service endpoint.
  • Un-marshalling of XML back into Java objects so that SOAP payloads returned from the remote service can be interpreted by our application.
  • Establishing and pooling HTTP connections between client and and web service.
WSDL2Java
Axis2 provides WSDL2Java tooling that parses a WSDL to generate the client side proxies required to invoke a remote service. WSDL2Java can be run on the command line but I prefer to use a Maven plugin so that I have a fresh set of proxies generated as part of every build.

Maven Configuration
The Maven POM configuration below shows how the axis2-wsdl2code-maven-plugin can be configured to generate the required client side stubs.
1:  <?xml version="1.0"?>  
2:  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0  
3:                                      http://maven.apache.org/maven-v4_0_0.xsd">  
4:       <artifactId>axis2-webservice-client-sample</artifactId>  
5:       <modelVersion>4.0.0</modelVersion>  
6:       <inceptionYear>2011</inceptionYear>  
7:       <packaging>jar</packaging>  
8:       <groupId>com.blog.webservices.client</groupId>  
9:       <version>1.0</version>  
10:       <properties>  
11:            <axis2.version>1.6.2</axis2.version>  
12:            <log4j.version>1.2.16</log4j.version>  
13:       </properties>  
14:       <build>  
15:            <resources>  
16:                 <resource>  
17:                      <directory>src/main/resources</directory>  
18:                      <filtering>true</filtering>  
19:                 </resource>  
20:            </resources>  
21:            <plugins>  
22:                 <plugin>  
23:                      <groupId>org.apache.axis2</groupId>  
24:                      <artifactId>axis2-wsdl2code-maven-plugin</artifactId>  
25:                      <version>1.6.2</version>  
26:                      <executions>  
27:                           <execution>  
28:                                <goals>  
29:                                     <goal>wsdl2code</goal>  
30:                                </goals>  
31:                                <configuration>  
32:                                     <wsdlFile>src/main/resources/wsdl/AccountDetailsService.wsdl</wsdlFile>  
33:                                     <databindingName>adb</databindingName>  
34:                                     <packageName>com.blog.webservices.client</packageName>  
35:                                     <outputDirectory>src/main/java</outputDirectory>  
36:                                     <flattenFiles>true</flattenFiles>  
37:                                </configuration>  
38:                           </execution>  
39:                      </executions>  
40:                 </plugin>  
41:                 <plugin>  
42:                      <groupId>org.apache.maven.plugins</groupId>  
43:                      <artifactId>maven-compiler-plugin</artifactId>  
44:                 </plugin>  
45:            </plugins>  
46:       </build>  
47:       <dependencies>  
48:            <dependency>  
49:                 <groupId>org.apache.axis2</groupId>  
50:                 <artifactId>axis2-kernel</artifactId>  
51:                 <version>${axis2.version}</version>  
52:            </dependency>  
53:            <dependency>  
54:                 <groupId>org.apache.axis2</groupId>  
55:                 <artifactId>axis2-adb</artifactId>  
56:                 <version>${axis2.version}</version>  
57:            </dependency>  
58:            <dependency>  
59:                 <groupId>org.apache.axis2</groupId>  
60:                 <artifactId>axis2-transport-http</artifactId>  
61:                 <version>${axis2.version}</version>  
62:            </dependency>  
63:            <dependency>  
64:                 <groupId>org.apache.axis2</groupId>  
65:                 <artifactId>axis2-transport-local</artifactId>  
66:                 <version>${axis2.version}</version>  
67:            </dependency>  
68:            <dependency>  
69:                 <groupId>org.apache.axis2</groupId>  
70:                 <artifactId>axis2-xmlbeans</artifactId>  
71:                 <version>${axis2.version}</version>  
72:            </dependency>  
73:            <dependency>  
74:                 <groupId>log4j</groupId>  
75:                 <artifactId>log4j</artifactId>  
76:                 <version>${log4j.version}</version>  
77:            </dependency>  
78:       </dependencies>  
79:  </project>  
The POM configuration shown here is pretty simple. The interesting bit is the WSDL2Code configuration plugin between lines 22 and 40. The plugin is configured with the following information

Line 32- Location of WSDL we're going to use for code generation.
Line 33 - Data binding framework we're going to use for code generation. In this example I've used the standard ADB (Axis Data Binding) framework but we could easily plugin an equivalent framework like JAXB or XMLBeans.
Line 34 - Package name for the generated classes.
Line 35 - Output directory for generated classes.

Code Generation
Now that we have the POM configured the next step is to run a build and generate our classes. When we run 'mvn clean install' the WSDL2Code plugin will read the WSDL and invoke the Axis code generator to build a client side proxy for our service. When the build is complete our project structure should look similar to figure 1.0. You'll notice that 2 classes were generated, both of which are explained below.
Figure 1.0 Project Structure

AccountDetailsServiceCallBackHandler
This is an abstract class that can be extend to implement a non blocking web service client. A non blocking client invokes the remote service on a separate thread and returns immediately, so as not to 'block' the client application while Axis waits on a response. The AccountDetailsServiceCallbackHandler abstract class should be extended to provide implementations for 2 callback methods, that are invoked by Axis once it has received a response from the service.
This non blocking approach is very useful in certain circumstances, for example, when a client application needs to call a number of different services at the same time. Rather than call each service sequentially, the client application can call multiple services simultaneously (on different threads) and handle the response from each service as it arrives, using appropriate callback implementations.

AccountDetailsServiceStub
This class acts as a client side proxy for the remote service and provides a means of building requests, invoking the service and processing responses.

Calling the Service Synchronously
The code sample below shows how we can use the generated classes to call our service synchronously. Note that the main thread will block while it waits on a response from the service.
1:  package com.blog.samples.webservices.client;  
2:  import java.rmi.RemoteException;  
3:  import com.blog.webservices.client.AccountDetailsServicesStub;  
4:  import com.blog.webservices.client.AccountDetailsServicesStub.AccountDetailsRequest;  
5:  import com.blog.webservices.client.AccountDetailsServicesStub.AccountDetailsResponse;  
6:  public class SynchronousWebServiceClientTest  
7:  {  
8:       public static void main (String [] args) throws RemoteException  
9:       {  
10:            AccountDetailsServicesStub servicesStub = new AccountDetailsServicesStub(WebServiceCientUtils.SERVICE_ENDPOINT);  
11:            AccountDetailsRequest accountDetailsRequest = new AccountDetailsRequest();  
12:            accountDetailsRequest.setAccountNumber("12345");  
13:            AccountDetailsResponse accountDetailsResponse = servicesStub.accountDetails(accountDetailsRequest);  
14:            WebServiceCientUtils.logAccountDetails(accountDetailsResponse.getAccountDetails());  
15:       }  
16:  }  
Calling the Service Asynchronously
The code sample below shows how we can call our service asynchronously. Invoking 'startService' on the service stub kicks off a web service request on a new thread and returns immediately so that execution of the client application is not blocked. We pass in a new instance of our callback handler to handle the web service response.
1:  package com.blog.samples.webservices.client;  
2:  import java.rmi.RemoteException;  
3:  import com.blog.webservices.client.AccountDetailsServicesStub;  
4:  import com.blog.webservices.client.AccountDetailsServicesStub.AccountDetailsRequest;  
5:  public class AsynchronousWebServiceClientTest  
6:  {  
7:       public static void main (String [] args) throws RemoteException, InterruptedException  
8:       {  
9:             AccountDetailsServicesStub servicesStub = new AccountDetailsServicesStub(WebServiceCientUtils.SERVICE_ENDPOINT);  
10:            AccountDetailsRequest accountDetailsRequest = new AccountDetailsRequest();  
11:            accountDetailsRequest.setAccountNumber("12345");  
12:            WebServiceCientCallBackHandler callBackHandler = new WebServiceCientCallBackHandler();  
13:            servicesStub.startaccountDetails(accountDetailsRequest, callBackHandler);  
14:            Thread.sleep(5000);  
15:       }  
16:  }  
A sample callback handler implementation is shown below. Note that we implement the receiveResultAccountDetails method to handle successful responses and receiveErrorAccountDetails to handle errors. Axis2 will invoke the appropriate method depending on whether or not the web service call was successful.
1:  package com.blog.samples.webservices.client;  
2:  import org.apache.log4j.Logger;  
3:  import com.blog.webservices.client.AccountDetailsServicesCallbackHandler;  
4:  import com.blog.webservices.client.AccountDetailsServicesStub.AccountDetailsResponse;  
5:  public class WebServiceCientCallBackHandler extends AccountDetailsServicesCallbackHandler  
6:  {  
7:            private static final Logger logger_c = Logger.getLogger(WebServiceCientCallBackHandler.class);  
8:            @Override  
9:            public Object getClientData()  
10:            {  
11:                 return super.getClientData();  
12:            }  
13:            @Override  
14:            public void receiveResultaccountDetails(AccountDetailsResponse result_p)  
15:            {  
16:                 super.receiveResultaccountDetails(result_p);  
17:                 WebServiceCientUtils.logAccountDetails(result_p.getAccountDetails());  
18:            }  
19:            @Override  
20:            public void receiveErroraccountDetails(Exception ex_p)  
21:            {  
22:                 super.receiveErroraccountDetails(ex_p);  
23:                 logger_c.error("An error occurred calling AccountDetails Service", ex_p);  
24:            }  
25:  }  
Source Code
You can download the full source code for this tutorial here. I'm having an issue with my GitHub account at the minute but as soon as I get that sorted I'll push the code up and add a link.
If you liked the post or have questions about any of the material covered, feel free to leave a comment below.

9 comments:

  1. Thank you! Your tutorials are very helpful :)

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hello again,

    I tried your little tutorial and I am getting the following exception

    org.apache.axis2.AxisFault: org.apache.axis2.databinding.ADBException: Unexpected subelement ....
    My ws are deployed on tomcat and are working fine because they were tested using SOAPUI.

    Any ideas, please?

    George

    ReplyDelete
    Replies
    1. I also tested with TCPMON and the results are ok.

      Delete
  4. Good stuff. Surprising difficult to get a simple example out of the Axis2 documentation - more about generation and tools than about how to use the actual generated output. Thanks for your write up - right on the money

    ReplyDelete
  5. Good toturial..
    Can you please also provide the contain of AccountDetailsServiceStub,so it can be more clear

    ReplyDelete
  6. Brian ... Stuff is very good. Need to know your source code does not have WebServiceCientUtils.java class which is being reference in Asynchronous and synchronous. One query do i need to add these classes in client project or webservice project.

    ReplyDelete
  7. Well said, the post is really the freshest on this valuable topic. I fit in with your conclusions and will thirstily look forward to your next updates
    host a website | hosting

    ReplyDelete