Leveraging Apache CXF and Maven to Generate Client Side Web Service Bindings

At a recent client, our team had the frequent need to integrate with internal and external systems via web services. Because we were consistently on very tight time lines and were not typically provided with web service client libraries, it became crucial to be able to quickly generate accurate client side bindings for the web services. Doing this by hand can be quite tedious, time consuming, and potentially error-prone.  The purpose of this article is to explain how to leverage Apache CXF and Maven to quickly generate client side web service bindings, and to detail a simple framework implemented on top of the generated classes to allow quick configuration of the client bindings at run time. In order illustrate thoroughly, a fictitious WSDL will be leveraged as a starting point.

Note that much of the information found in this article can be found in various Apache CXF documentation pages, including:

1. Overview

Three technologies will be leveraged to generate the web service bindings/domain classes:

  • Apache Maven
  • Apache CXF
  • JAXB

Maven is optional, but for the purposes of this discussion it will be assumed that it is being used.

The Apache CXF wsdl2java plug-in is used to generate the client side web service bindings. Leveraging the plug in, the code generation can be executed via the Maven build. The plug-in allows for a variety of optional parameters to be specified in order to control how the web service binding get generated. The resulting classes are built on CXF and JAXB libraries.

2. Example WSDL

The following example WSDL will be used facilitate the illustration of the use of wsdl2java and Maven:



<?xml version="1.0" encoding="utf-8" standalone="no"?>
<wsdl:definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
    xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:tns="http://test.test.com/test_api/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://test.test.com/test_api/">
    <wsdl:documentation>Test WSDL for Illustration of wsdl2java usage with maven</wsdl:documentation>
    <wsdl:types>
        <s:schema elementFormDefault="qualified" targetNamespace="http://test.test.com/test_api/">
            <s:element name="TestElement1">
                <s:complexType>
                    <s:sequence>
                        <s:element maxOccurs="1" minOccurs="0" name="type1" type="tns:Type1" />
                        <s:element maxOccurs="1" minOccurs="0" name="type2" type="tns:Type2Request" />
                    </s:sequence>
                </s:complexType>
            </s:element>
            <s:complexType name="Type1">
                <s:sequence>
                    <s:element maxOccurs="1" minOccurs="0" name="Type1Field1" type="s:string" />
                    <s:element maxOccurs="1" minOccurs="0" name="Type1Field2" type="s:string" />
                    <s:element maxOccurs="1" minOccurs="0" name="Type1Field3" type="s:string" />
                </s:sequence>
            </s:complexType>
            <s:complexType name="Type2Request">
                <s:sequence>
                    <s:element maxOccurs="1" minOccurs="0" name="Type2Field1" type="s:string" />
                    <s:element maxOccurs="1" minOccurs="0" name="Type2Field2" type="s:string" />
                    <s:element maxOccurs="1" minOccurs="0" name="Type2Field3" type="s:string" />
                </s:sequence>
            </s:complexType>
            <s:element name="Type2Response">
                <s:complexType>
                    <s:sequence>
                        <s:element maxOccurs="1" minOccurs="1" name="Type2ResponseField1" type="s:string" />
                        <s:element maxOccurs="1" minOccurs="0" name="Type2ResponseField2" type="s:string" />
                        <s:element maxOccurs="1" minOccurs="0" name="Type2ResponseField3" type="s:string" />
                    </s:sequence>
                </s:complexType>
            </s:element>
        </s:schema>
    </wsdl:types>
    <wsdl:message name="TestOperationSoapIn">
        <wsdl:part element="tns:TestElement1" name="parameters" />
    </wsdl:message>
    <wsdl:message name="TestOperationSoapOut">
        <wsdl:part element="tns:Type2Response" name="parameters" />
    </wsdl:message>
    <wsdl:portType name="Test_Operation_ManagerSoap">
        <wsdl:operation name="TestOperation">
            <wsdl:documentation>A random test operation</wsdl:documentation>
            <wsdl:input message="tns:TestOperationSoapIn" />
            <wsdl:output message="tns:TestOperationSoapOut" />
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="Test_Operation_ManagerSoap" type="tns:Test_Operation_ManagerSoap">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="TestOperation">
            <soap:operation soapAction="http://test.test.com/test_api/TestOperation" style="document" />
            <wsdl:input>
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:binding name="Test_Operation_ManagerSoap12" type="tns:Test_Operation_ManagerSoap">
        <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="TestOperation">
            <soap12:operation soapAction="http://test.test.com/test_api/TestOperation"
                style="document" />
            <wsdl:input>
                <soap12:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap12:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="Test_Operation_Manager">
        <wsdl:documentation>Random test operation api</wsdl:documentation>
        <wsdl:port binding="tns:Test_Operation_ManagerSoap" name="Test_Operation_ManagerSoap">
            <soap:address location="https://test.test.com/test-api.asmx" />
        </wsdl:port>
        <wsdl:port binding="tns:Test_Operation_ManagerSoap12" name="Test_Operation_ManagerSoap12">
            <soap12:address location="https://test.test.com/test-api.asmx" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

In the web service client bindings, the goal is to generate classes for each of the types defined in the wsdl:types section of the WSDL, and to generate a service class for the Test_Operation_Manager service in the WSDL. Additionally, based on the contents of the WSDL, we expect there to be soap 1.1 and 1.2 bindings.

3. Maven POM

Much of the work will be done via the maven configuration in the POM.xml file, specifically within the CXF codegen (wsdl2java) plug in. A sample POM.xml enabling generation of client binding code for the above WSDL follows:



<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 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <cxf.version>2.2.9</cxf.version>
        <java.version>1.6</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <verbose>true</verbose>
                    <fork>true</fork>
                    <compilerVersion>${java.version}</compilerVersion>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-codegen-plugin</artifactId>
                <version>${cxf.version}</version>
                <executions>
                    <execution>
                        <id>test-service</id>
                        <phase>generate-sources</phase>
                        <configuration>
                            <sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
                            <wsdlOptions>
                                <wsdlOption>
                                    <wsdl>${basedir}/src/main/resources/test.wsdl</wsdl>
                                    <serviceName>Test_Operation_Manager</serviceName>
                                    <bindingFiles>
                                        <bindingFile>${basedir}/src/main/resources/test_binding.xml</bindingFile>
                                    </bindingFiles>
                                    <extraargs>
                                        <extraarg>-xjc-Xts</extraarg>
                                    </extraargs>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>xerces</groupId>
                        <artifactId>xercesImpl</artifactId>
                        <version>2.9.1</version>
                    </dependency>
                    <dependency>
                        <groupId>org.apache.cxf</groupId>
                        <artifactId>cxf-xjc-ts</artifactId>
                        <version>${cxf.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-common-utilities</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-tools-common</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-simple</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>
    </dependencies>
</project>

Note that the wsdl2java tool can be executed from the command line, so maven is not technically required for the generation of client code bindings.

The following elements within the configuration require some additional explanation:

  • The compiler must be Java 1.5 or 1.6. This is because the generated code will leverage JAXB annotations heavily, which requires at least Java 1.5. The Java run time will also need to be at least 1.5.
  • Depending on how you want to use/configure the generated services, you may need additional CXF dependencies (for instance, depending security requirements for the services). There is a cxf-bundle dependency that can be included, which will include all CXF libraries, but will also require a number of other dependencies to be included.
  • phase: If you look at the documentation and examples for wsdl2java, the phase is generally specified as “generate-sources”, whereas in the above configuration we have specified “generate-resources”. This is done to avoid build errors. The generate-sources phase executes twice during the maven build (at least when running mvn clean install). Depending on the nature of the project and WSDL, when the wsdl2java plug in is executed twice during the build, the build may fail during the second execution due to package conflicts. The generate-resources phase is only executed once, so by moving the phase to generate-resources, we avoid possible build errors.
  • sourceRoot: This is where the generated code will be placed. In the example above, the code would get placed under target/generated/src/main/java within the project’s directory structure.
  • wsdlOptions: You can specify any number of WSDLs /services to generated binding for within WSDL options. Only one is illustrated above.
  • extra args: There are a variety of additional arguments that can be specified within this element to indicate how the code should be generated. For instance:


<extraarg>-xjc-Xts</extraarg>

This will force toString methods to be generated for mapped data types and elements (they are not generated be default). A full listing of possible values for extra arguments can be found in the last section of http://cxf.apache.org/docs/maven-cxf-codegen-plugin-wsdl-to-java.html.

  • Depending on the WSDL options you include (if you choose to include any), you may need to add dependencies to your build. In the above file, because the WSDL option that generates toString methods has been included, it is also necessary to include the cxf-common-utilities dependency in the build (which contains JAXB classes used within the generated toString methods).
  • wsdl: You can specify any valid location for the WSDL. This could be a path within your project’s directory structure, or the path to the published WSDL. In the above configuration, the location of the WSDL used for code generated is within the project. It is also possible to load the WSDL from a maven repository (See section 5 of http://cxf.apache.org/docs/maven-cxf-codegen-plugin-wsdl-to-java.html).
  • binding file: A binding file is optional, but there is quite a bit you can configure in a JAX-WS binding file. For instance, you can specify that asynchronous web service client methods should be generated. A full list of the possible configurations that can be place in the binding file can be found at http://jax-ws.java.net/jax-ws-20-fcs/docs/customizations.html. Here is a simple example of a binding file that will instruct wsdl2java to generate asynchronous web service bindings:


<bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://java.sun.com/xml/ns/jaxws">
  <bindings node="wsdl:definitions">
    <enableAsyncMapping>true</enableAsyncMapping>
  </bindings>
</bindings>

4. Output from wsdl2java

The output of this portion of the build will be domain objects and services corresponding to everything defined in the specified WSDL. The outputs are logically separated by package name, based on the contents of the WSDL. The generated code leverages JAXB for binding, and is almost entirely annotation driven. It should be noted that JAXB is not the only option available for binding (see http://cxf.apache.org/docs/databindings.html).

Here is an example of one of the generated domain classes:



package com.test.test.test_api;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import org.apache.cxf.jaxb.JAXBToStringBuilder;
import org.apache.cxf.jaxb.JAXBToStringStyle;

/**
 * <p>
 * Java class for Type2Request complex type.
 *
 * <p>
 * The following schema fragment specifies the expected content contained within
 * this class.
 *
 * <pre>
 * &lt;complexType name="Type2Request">
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element name="Type2Field1" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
 *         &lt;element name="Type2Field2" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
 *         &lt;element name="Type2Field3" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 *
 *
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Type2Request", propOrder = { "type2Field1", "type2Field2",
    "type2Field3" })
public class Type2Request {

  @XmlElement(name = "Type2Field1")
  protected String type2Field1;
  @XmlElement(name = "Type2Field2")
  protected String type2Field2;
  @XmlElement(name = "Type2Field3")
  protected String type2Field3;

  /**
   * Gets the value of the type2Field1 property.
   *
   * @return possible object is {@link String }
   *
   */
  public String getType2Field1() {
    return type2Field1;
  }

  /**
   * Sets the value of the type2Field1 property.
   *
   * @param value
   *          allowed object is {@link String }
   *
   */
  public void setType2Field1(String value) {
    this.type2Field1 = value;
  }

  /**
   * Gets the value of the type2Field2 property.
   *
   * @return possible object is {@link String }
   *
   */
  public String getType2Field2() {
    return type2Field2;
  }

  /**
   * Sets the value of the type2Field2 property.
   *
   * @param value
   *          allowed object is {@link String }
   *
   */
  public void setType2Field2(String value) {
    this.type2Field2 = value;
  }

  /**
   * Gets the value of the type2Field3 property.
   *
   * @return possible object is {@link String }
   *
   */
  public String getType2Field3() {
    return type2Field3;
  }

  /**
   * Sets the value of the type2Field3 property.
   *
   * @param value
   *          allowed object is {@link String }
   *
   */
  public void setType2Field3(String value) {
    this.type2Field3 = value;
  }

  /**
   * Generates a String representation of the contents of this type. This is an
   * extension method, produced by the 'ts' xjc plugin
   *
   */
  @Override
  public String toString() {
    return JAXBToStringBuilder.valueOf(this, JAXBToStringStyle.DEFAULT_STYLE);
  }

}

Next, lets look at an example of a service class that gets generated. There are actually two pertinent classes generated:

  • A service class where you can configure the service (e.g., bind to a port, and so on). Here is the one generated for the test service in the WSDL (Test_Operation_Manager):


package com.test.test.test_api;

import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.Service;

/**
 * This class was generated by Apache CXF 2.2.9 Wed Sep 15 10:57:23 CDT 2010
 * Generated source version: 2.2.9
 *
 */

@WebServiceClient(name = "Test_Operation_Manager", wsdlLocation = "http://www.objectpartners.com/test/test.wsdl", targetNamespace = "http://test.test.com/test_api/")
public class TestOperationManager extends Service {

  public final static URL WSDL_LOCATION;
  public final static QName SERVICE = new QName(
      "http://test.test.com/test_api/", "Test_Operation_Manager");
  public final static QName TestOperationManagerSoap = new QName(
      "http://test.test.com/test_api/", "Test_Operation_ManagerSoap");
  public final static QName TestOperationManagerSoap12 = new QName(
      "http://test.test.com/test_api/", "Test_Operation_ManagerSoap12");
  static {
    URL url = null;
    try {
      url = new URL(
          "http://www.objectpartners.com/test/test.wsdl");
    } catch (MalformedURLException e) {
      System.err
          .println("Can not initialize the default wsdl from http://www.objectpartners.com/test/test.wsdl");
      // e.printStackTrace();
    }
    WSDL_LOCATION = url;
  }

  public TestOperationManager(URL wsdlLocation) {
    super(wsdlLocation, SERVICE);
  }

  public TestOperationManager(URL wsdlLocation, QName serviceName) {
    super(wsdlLocation, serviceName);
  }

  public TestOperationManager() {
    super(WSDL_LOCATION, SERVICE);
  }

  /**
   *
   * @return returns TestOperationManagerSoap
   */
  @WebEndpoint(name = "Test_Operation_ManagerSoap")
  public TestOperationManagerSoap getTestOperationManagerSoap() {
    return super.getPort(TestOperationManagerSoap,
        TestOperationManagerSoap.class);
  }

  /**
   *
   * @param features
   *          A list of {@link javax.xml.ws.WebServiceFeature} to configure on
   *          the proxy. Supported features not in the <code>features</code>
   *          parameter will have their default values.
   * @return returns TestOperationManagerSoap
   */
  @WebEndpoint(name = "Test_Operation_ManagerSoap")
  public TestOperationManagerSoap getTestOperationManagerSoap(
      WebServiceFeature... features) {
    return super.getPort(TestOperationManagerSoap,
        TestOperationManagerSoap.class, features);
  }

  /**
   *
   * @return returns TestOperationManagerSoap
   */
  @WebEndpoint(name = "Test_Operation_ManagerSoap12")
  public TestOperationManagerSoap getTestOperationManagerSoap12() {
    return super.getPort(TestOperationManagerSoap12,
        TestOperationManagerSoap.class);
  }

  /**
   *
   * @param features
   *          A list of {@link javax.xml.ws.WebServiceFeature} to configure on
   *          the proxy. Supported features not in the <code>features</code>
   *          parameter will have their default values.
   * @return returns TestOperationManagerSoap
   */
  @WebEndpoint(name = "Test_Operation_ManagerSoap12")
  public TestOperationManagerSoap getTestOperationManagerSoap12(
      WebServiceFeature... features) {
    return super.getPort(TestOperationManagerSoap12,
        TestOperationManagerSoap.class, features);
  }

}

Only a single instance of this service class needs to be configured within an application.

  • Second, the service methods reside in a different INTERFACE, in this case TestOperationManagerSoap (shown below). Note that a concrete implementation of this interface is not required.  CXF will take care of that at runtime.


package com.test.test.test_api;

import java.util.concurrent.Future;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;

/**
 * This class was generated by Apache CXF 2.2.9 Wed Sep 15 10:57:23 CDT 2010
 * Generated source version: 2.2.9
 *
 */

@WebService(targetNamespace = "http://test.test.com/test_api/", name = "Test_Operation_ManagerSoap")
@XmlSeeAlso( { ObjectFactory.class })
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface TestOperationManagerSoap {

  @WebMethod(operationName = "TestOperation")
  public Response<com.test.test.test_api.Type2Response> testOperationAsync(
      @WebParam(partName = "parameters", name = "TestElement1", targetNamespace = "http://test.test.com/test_api/") TestElement1 parameters);

  @WebMethod(operationName = "TestOperation")
  public Future<?> testOperationAsync(
      @WebParam(partName = "parameters", name = "TestElement1", targetNamespace = "http://test.test.com/test_api/") TestElement1 parameters,
      @WebParam(name = "asyncHandler", targetNamespace = "") AsyncHandler<com.test.test.test_api.Type2Response> asyncHandler);

  @WebResult(name = "Type2Response", targetNamespace = "http://test.test.com/test_api/", partName = "parameters")
  @WebMethod(operationName = "TestOperation", action = "http://test.test.com/test_api/TestOperation")
  public Type2Response testOperation(
      @WebParam(partName = "parameters", name = "TestElement1", targetNamespace = "http://test.test.com/test_api/") TestElement1 parameters);
}

Taking a look at the methods generated above, you will see:

  1. testOperationAsync – This is the “fire and forget” version of the service method (see section 7 below)
  2. testOperationAsync where you pass in an AsyncHandler instance – This is the version where the web service is called asynchronously, and the AsyncHandler is then executed as a callback on the response when it is received. (See section 8 below)
  3. testOperation – This is the synchronous version of the methods (see section 6 below)

5. Configuring the ports:

Here is an example of how to configure the port for the service. First,  a service “configurator” will be defined. This class is responsible for tasks such as security and transport configuration, as well as configuration of any necessary interceptors for the framework.  Note that  you can append interceptors for a service within this implementation as needed (in the example below only logging interceptor are appended). Within the following class, it is assumed that the service will require basic username/password authentication, and that the credentials will be wired in via Spring or obtained via some other mechanism.



package cxf.test;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HttpBasicAuthSupplier;
import org.apache.cxf.message.Message;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.interceptor.LoggingInInterceptor;

import java.net.URL;

/**
 * General purpose configurator for generated apache CXF web service client
 * implementations that require basic username/password authentication
 */
public class ServiceConfigurator {

  private String basicAuthUser;
  private String basicAuthPassword;

  public void configure(Object service) {

    Client client = ClientProxy.getClient(service);

    HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
    httpConduit.setAuthSupplier(new HttpBasicAuthSupplier() {
      @Override
      public UserPass getPreemptiveUserPass(String s, URL url, Message message) {
        return createUserPass(getBasicAuthUser(), getBasicAuthPassword());
      }

      @Override
      public UserPass getUserPassForRealm(String s, URL url, Message message,
          String s1) {
        return createUserPass(getBasicAuthUser(), getBasicAuthPassword());
      }
    });

    client.getOutInterceptors().add(new LoggingOutInterceptor());
    client.getInInterceptors().add(new LoggingInInterceptor());
    client.getInFaultInterceptors().add(new LoggingInInterceptor());
  }

  public String getBasicAuthPassword() {
    return basicAuthPassword;
  }

  public void setBasicAuthPassword(String basicAuthPassword) {
    this.basicAuthPassword = basicAuthPassword;
  }

  public String getBasicAuthUser() {
    return basicAuthUser;
  }

  public void setBasicAuthUser(String basicAuthUser) {
    this.basicAuthUser = basicAuthUser;
  }
}

Second, the service instances needs to be created and configured. To accomplish this, something similar to the following can be implemented:



package cxf.test;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Executors;

import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;

import com.test.test.test_api.TestElement1;
import com.test.test.test_api.TestOperationManager;
import com.test.test.test_api.TestOperationManagerSoap;
import com.test.test.test_api.Type1;
import com.test.test.test_api.Type2Request;
import com.test.test.test_api.Type2Response;

/**
 * Illustrates the use of the web service client code generated by the Apache CXF
 * wsdl2java plug-in, as well as how to configure the services.
 *
 * Illustrates the use for the synchronous, "fire-and-forget", and fully
 * asynchronous versions of the test service endpoint.
 */
public class TestService {

  ServiceConfigurator serviceConfigurator;

  public TestOperationManagerSoap service;

  /**
   * Get the Test service instance for use. If the instance does not create yet,
   * create it.
   *
   * @return The service instance
   */
  public TestOperationManagerSoap getService() {
    if (service == null) {
      service = createTestOperationManagerInstance();
    }

    return service;
  }

/**
   * Creates and returns the instance of the {@link TestOperationManager}
   *
   * @return instance of the {@link TestOperationManager}
   */
  public TestOperationManagerSoap createTestOperationManagerInstance() {
    TestOperationManager service = new TestOperationManager(
        getWsdlLocation(TestOperationManager.WSDL_LOCATION));

    service.setExecutor(Executors.newCachedThreadPool());

    TestOperationManagerSoap testOperationManagerSoap = service
        .getTestOperationManagerSoap();

    getServiceConfigurator().configure(testOperationManagerSoap);

    return testOperationManagerSoap;
  }

  /**
   * Used to get the environment-specific location of the wsdl. If there is no
   * concept of an environment-specific location of the wsdl, then just return
   * the input codedWSDL
   */
  public URL getWsdlLocation(URL codedWSDL) {
    URL wsdlLocation = null;
    try {
      wsdlLocation = new URL("http://test.test.com/test-api/test");
    } catch (MalformedURLException mue) {
      wsdlLocation = codedWSDL;
    }

    return wsdlLocation;

  }

  public ServiceConfigurator getServiceConfigurator() {
    return serviceConfigurator;
  }

  public void setServiceConfigurator(ServiceConfigurator serviceConfigurator) {
    this.serviceConfigurator = serviceConfigurator;
  }
}

A few notes about this implementation:

  1. The purpose of getWsdlLocation is to specify where the WSDL is actually located. A default location based on the inputs to the wsdl2java plugin will have been generated within the binding classes. However, it may be that you want to specify an environment-specific or alternate location. This method would be where you implement such logic.
  2. When we set the executor on the service, we are setting the executor that will be used for asynchronous processing (see sections 7 and 8 below). This executor plays no role in synchronous web service calls. Here we leverage a cached thread pool, but any type of executor can be specified.
  3. There only needs to be one instance of the web service client.

6. Leveraging the Synchronous Versions of the Web Service Client Bindings

Pretty simple. Something to the effect of the following, placed in the service class (e.g., TestService from section 5 above):



/**
   * Perform the synchronous call to the web service endpoint. The method blocks
   * until the web service call is completed, and the unmarshalled response is
   * returned.
   *
   *
   * @return The response from the web service call
   */
  public Type2Response requestSynchronous(String type1Field1,
      String type1Field2, String type1Field3, String type2Field1,
      String type2Field2, String type2Field3) {

    TestElement1 testElement1 = new TestElement1();

    Type1 type1 = new Type1();
    type1.setType1Field1(type1Field1);
    type1.setType1Field2(type1Field2);
    type1.setType1Field3(type1Field3);
    testElement1.setType1(type1);

    Type2Request type2Request = new Type2Request();
    type2Request.setType2Field1(type2Field1);
    type2Request.setType2Field2(type2Field2);
    type2Request.setType2Field3(type2Field3);

    testElement1.setType2(type2Request);

    return getService().testOperation(testElement1);
  }

7. Leveraging the Asynchronous Versions of the Web Service Client Bindings

For the asynchronous version with no callback (fire and forget), it is again very simple:



 /**
   * Perform the asynchronous "fire and forget" call to the web service
   * endpoint. The method does not block, does not wait for the response, and
   * essentially ignores the response. This is not an appropriate version of the
   * method to use if there is any reason to be concerned about what the
   * response might contain.
   *
   *
   */
  public void requestAsyncFireAndForget(String type1Field1, String type1Field2,
      String type1Field3, String type2Field1, String type2Field2,
      String type2Field3) {

    TestElement1 testElement1 = new TestElement1();

    Type1 type1 = new Type1();
    type1.setType1Field1(type1Field1);
    type1.setType1Field2(type1Field2);
    type1.setType1Field3(type1Field3);
    testElement1.setType1(type1);

    Type2Request type2Request = new Type2Request();
    type2Request.setType2Field1(type2Field1);
    type2Request.setType2Field2(type2Field2);
    type2Request.setType2Field3(type2Field3);

    testElement1.setType2(type2Request);

    getService().testOperationAsync(testElement1);
  }

Of course, in this case we cannot get the response, because we fire and forget. So we don’t block until the web service call is completed, we just continue on, with the assumption that the web service call will proceed as planned, and that we don’t care about the response.

8. Leveraging the Asynchronous Web Service Client Bindings With Callbacks

In this case, you need to define a callback handler first. The callback handler will implement the code that must be used to process the result of the web service call, and must implement the interface AsyncHandler<T>, where <T> is the web service response type. Here is a rudimentary callback handler for use with our test operation:



/**
   * Very basic callback handler for use when leveraging the asynchronous
   * version of the testOperation web serivce client.
   *
   */
  public class TestOperationAsyncCallback implements
      AsyncHandler<Type2Response> {

    @Override
    public void handleResponse(Response<Type2Response> res) {
      System.out.println("Do something useful with this response: "
          + res.toString());
    }

  }

And here is the method that is used to make the call:



/**
   * Perform the fully asynchronous call to the web service endpoint. The method
   * does not block, and does not wait for the response. The response will be
   * handled by a callback handler that implements the {@link AsyncHandler}
   * interface, such as {@link TestOperationAsyncCallback} shown later in this
   * class
   *
   */
  public void requestAsyncWithCallback(String type1Field1, String type1Field2,
      String type1Field3, String type2Field1, String type2Field2,
      String type2Field3) {

    TestElement1 testElement1 = new TestElement1();

    Type1 type1 = new Type1();
    type1.setType1Field1(type1Field1);
    type1.setType1Field2(type1Field2);
    type1.setType1Field3(type1Field3);
    testElement1.setType1(type1);

    Type2Request type2Request = new Type2Request();
    type2Request.setType2Field1(type2Field1);
    type2Request.setType2Field2(type2Field2);
    type2Request.setType2Field3(type2Field3);

    testElement1.setType2(type2Request);

    getService().testOperationAsync(testElement1,
        new TestOperationAsyncCallback());
  }

8. Summary and Final Notes

This article has illustrated how to leverage Apache CXF, Maven, and JAXB to rapidly create accurate web service client bindings. Additionally, a small framework to allow an application to quickly configure and leverage the generated bindings has been detailed. With the use of these tools, it only takes a matter of minutes to generated client side bindings for virtually any web service. Depending on the integration requirements, quite a bit more configuration is possible both within the maven configuration and within an associated JAX-WS binding file.

There is one potential issue with the wsdl2java code generator: the WSDL in question must be well-formed, without containing any elements or types that would cause name clashes in the generated code. For instance, if the WSDL in question has an element named A, and a complex type named A, the code generation will fail. If this occurs, and you are not the owner of that WSDL, a possible solution is to generate the code off of a local, modified version of the WSDL.

One thought on “Leveraging Apache CXF and Maven to Generate Client Side Web Service Bindings

  1. youthflies says:

    Hi,cxf-codegen-plugin helps me a lot, but if the webservice is protected by password, how can I generate local webservice code with cxf-codegen-plugin, thinks a lot.

  2. Petr says:

    1. You do not specifed that sample wsdl should be placed in test.wsdl
    2. Where does test_binding.xml file come from?

    So, useful, but incomplete article.

  3. Petr says:

    pom.xml misses

    org.apache.cxf
    cxf-rt-frontend-jaxws
    2.2.6

    as explained here

    http://www.spudsoft.co.uk/2010/03/lsned-29-persuading-jdk-6-to-use-cxf-classes-rather-than-its-own-avoiding-seistub

    So many misses. Seems like a non tested thing.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*