Contact Us 1-800-596-4880

Connector to a SOAP Service via CXF Client Example

This example demonstrates the steps to build a connector for a SOAP service, given a WSDL file. The client is built using Apache CXF, which can generate most of the required code for you from the WSDL metadata.

Apache CXF is a complex framework that enables many different models for accessing web services. The focus here is on a straightforward path that should produce results for any service for which you have a WSDL. See the Apache CXF documentation Building Clients and, especially, Developing a Consumer with CXF, for more details on the full range of possible client models using CXF.

The model for development is:

  • Create the major classes that make up the connector

  • Add authentication-related functionality to the @Connector class

  • Apply a test-driven development process to add individual operations to the connector

Assumptions

This document assumes:

CXF-Based Connector Architecture

Follow the steps in this document to build the connector client, starting with the WSDL that describes the service.

Starting with a new connector project (possibly with authentication logic built in):

  • Get a WSDL file for the service and add it to the project

  • Run wsdl2java from Maven to generate CXF stub client code that can call the service operations

  • Write a proxy class that wraps the stub client

  • Build a DevKit Connector class whose methods call methods on the proxy class

The Example Service: SunSetRiseService

The web service for the demo is the SunSetRise Web service at WebserviceX.net.

The request and response are both represented by an XML document that specifies:

  • A location in latitude and longitude coordinates to indicate where you are located. Note that you need use negative latitude values for Southern Hemisphere and negative longitude values for Eastern Hemisphere. Coordinates are in decimal, rather than degrees, minutes and seconds.

  • A date to indicate for which date you want sunrise and sunset times to be returned.

  • A GMT time zone to indicate which time zone to translate the results to (offset from GMT).

Here is a sample request message of GetSunSetRiseTime in SOAP 1.1 to get sunrise and sunset times:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
   xmlns:web="http://www.webserviceX.NET/">
   <soap:Header/>
   <soap:Body>
      <web:GetSunSetRiseTime>
         <web:L>
            <web:Latitude>0.0</web:Latitude>
            <web:Longitude>0.0</web:Longitude>
            <web:TimeZone>0</web:TimeZone>
            <web:Day>21</web:Day>
            <web:Month>3</web:Month>
            <web:Year>2011</web:Year>
         </web:L>
      </web:GetSunSetRiseTime>
   </soap:Body>
</soap:Envelope>

The SunSetTime and SunRiseTime elements are omitted because those are computed by the service. The response will have those populated.

Here is a sample response:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
    xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetSunSetRiseTimeResponse
            xmlns="http://www.webserviceX.NET/">
            <GetSunSetRiseTimeResult>
                <Latitude>0</Latitude>
                <Longitude>0</Longitude>
                <SunSetTime>17.9877033</SunSetTime>
                <SunRiseTime>5.87441826</SunRiseTime>
                <TimeZone>0</TimeZone>
                <Day>21</Day>
                <Month>3</Month>
                <Year>2011</Year>
            </GetSunSetRiseTimeResult>
        </GetSunSetRiseTimeResponse>
    </soap:Body>
</soap:Envelope>

SunSetTime and SunRiseTime are in decimal, rather than hours, minutes and seconds.

Creating the CXF Stub Client from WSDL

All SOAP APIs provide a WSDL file that defines how and at what endpoint and ports a SOAP web service can be called, what operations and parameters it expects, and what data types (simple or complex) the operations return.

CXF includes the wsdl2java utility that can generate Java stub client code to call any method on the service, and marshal and unmarshal request parameters and responses as Java objects for further processing. This generated stub client will be the core of your connector.

The steps to create the stub client and add it to your project are provided below.

Preparations

You must download a copy of the WSDL file to your local disk and include it in your Maven project. For the Web service in this example, you can download the WSDL here: http://www.webservicex.net/sunsetriseservice.asmx?WSDL.

See Preparing API Access or a Sandbox Environment for steps that may be required to gain access to other APIs, including how to get access to the WSDL file.

Step 1 Adding the WSDL File to Your Project

In your project, under /src/main/resources , create a subdirectory wsdl and copy your WSDL there.

For this example, copy the WSDL to /src/main/resources/wsdl/sunsetriseservice.wsdl.

You must save this WSDL file as a local file in your project. The CXF release included with Mule fails if you reference a remote WSDL by its URL directly.

Step 2 Updating Your POM File

The default POM file (where Maven stores all instructions for the build) does not include properties, dependencies and Maven plugins specific to accessing SOAP using CXF. You must add these manually into your pom.xml file.

Adding WSDL and CXF Properties to POM

The first block of code adds several properties to your POM, to identify the CXF version to use, set the package name, and specify the location of the WSDL in the project and in the connector jar file:

SOAP CXF Connector: Maven Properties

<!-- Maven should build the update site Zip file -->
<devkit.studio.package.skip>false</devkit.studio.package.skip>

<!--  CXF version info -->
<cxf.version>2.5.9</cxf.version>
<cxf.version.boolean>2.6.0</cxf.version.boolean>

<!-- Package name, WSDL file path and location in the JAR -->
<connector.package>
    org.tutorial.sunsetrise.definition
</connector.package>
<connector.wsdl>
    ${basedir}/src/main/resources/wsdl/sunsetriseservice.wsdl
</connector.wsdl>
<connector.wsdlLocation>
    classpath:wsdl/sunsetriseservice.wsdl
</connector.wsdlLocation>

Add these elements within the <properties> element., and update connector.wsdl and connector.wsdlLocation to reflect the name of your WSDL file.

Adding a Maven Dependency on CXF

The second POM update adds a dependency on the CXF module included in Mule:

CXF Dependency

<dependency>
    <groupId>org.mule.modules</groupId>
    <artifactId>mule-module-cxf</artifactId>
    <version>${mule.version}</version>
    <scope>provided</scope>
  </dependency>

Copy and paste this block of code inside the <dependencies> tag, near the end of the file, alongside the other <dependency> elements that are already listed. You will not have to edit this block, just add it.

Adding a Maven Plugin for wsdl2java

The third POM update is a wsdl2java Maven plugin, that will generate Java classes from the WSDL file. Paste this plugin element in the <plugins> element inside the <build> element. (Make sure you don’t place it in the <pluginManagement> element.)

You will not have to edit this block, just add it.

Wsdl2Java

<plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-codegen-plugin</artifactId>
                <version>${cxf.version}</version>
                <executions>
                    <execution>
                        <!-- Note that validate phase is not the usual phase to run wsdl2java...
                            this is done because DevKit requires the class be generated so it can be used in generate-sources phase by devkit. The devkit generated code from annotations etc. references the wsdl2java generated output  -->
                        <phase>validate</phase>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                        <configuration>
                            <wsdlOptions>
                                <wsdlOption>
                                    <!-- wsdl file path -->
                                    <wsdl>${connector.wsdl}</wsdl>
                                    <!-- pick up the WSDL from within the JAR -->
                                    <wsdlLocation>${connector.wsdlLocation}</wsdlLocation>
                                    <autoNameResolution>true</autoNameResolution>
                                    <extraargs>
                                        <!-- Package Destination -->
                                        <extraarg>-p</extraarg>
                                        <!-- name of the output package specified following -p argument
                                            to wsdl2java -->
                                        <extraarg>
                                            ${connector.package}
                                        </extraarg>
                                            <!-- DataMapper compatibility requires that boolean getters and setters
                                            follow naming convention for other getters and setters. -->
                                        <extraarg>-xjc-Xbg</extraarg>
                                        <extraarg>-xjc-Xcollection-setter-injector</extraarg>
                                    </extraargs>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <!-- Boolean getters -->
                    <dependency>
                        <groupId>org.apache.cxf.xjcplugins</groupId>
                        <artifactId>cxf-xjc-boolean</artifactId>
                        <version>${cxf.version.boolean}</version>
                    </dependency>
                    <!-- Collection Setters -->
                    <dependency>
                        <groupId>net.java.dev.vcc.thirdparty</groupId>
                        <artifactId>collection-setter-injector</artifactId>
                        <version>0.5.0-1</version>
                    </dependency>
                </dependencies>
            </plugin>

Things to notice:

  • The connector.package, connector.wsdl and connector.wsdlLocation properties you added are referenced here.

  • The xjc-Xbg argument is included so that wsdl2java to generate getters and setters that follow the naming convention of other Java bean getters and setters. This is required for compatibility with DataSense and DataMapper.

  • The wsdl2java code generation is performed during the Maven validate phase. The generated code from wsdl2java is required in the generate-sources phase of the build process, where DevKit code generation references these sources.

Below is the full pom.xml file contents with the required changes for this tutorial.

Complete POM file

<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.tutorial</groupId>
    <artifactId>sunsetriseconnector</artifactId>
    <version>1.0</version>
    <packaging>mule-module</packaging>
    <name>Sunset Sunrise Service</name>
    <properties>
        <mule.version>3.4.0</mule.version>
        <mule.devkit.version>3.4.0</mule.devkit.version>
        <junit.version>4.9</junit.version>
        <mockito.version>1.8.2</mockito.version>
        <jdk.version>1.6</jdk.version>

        <!-- Maven should build the update site Zip file -->
        <devkit.studio.package.skip>false</devkit.studio.package.skip>
        <!--  CXF version info -->
        <cxf.version>2.5.9</cxf.version>
        <cxf.version.boolean>2.6.0</cxf.version.boolean>
        <!-- WSDL file path and location in the JAR -->
        <connector.wsdl>
            ${basedir}/src/main/resources/wsdl/sunsetriseservice.wsdl
        </connector.wsdl>
        <connector.wsdlLocation>
            classpath:wsdl/sunsetriseservice.wsdl
        </connector.wsdlLocation>
        <connector.package>
            org.tutorial.sunsetrise.definition
        </connector.package>
    </properties>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.mule.tools.devkit</groupId>
                    <artifactId>mule-devkit-maven-plugin</artifactId>
                    <version>${mule.devkit.version}</version>
                    <extensions>true</extensions>
                </plugin>
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>org.mule.tools.devkit</groupId>
                                        <artifactId>mule-devkit-maven-plugin</artifactId>
                                        <versionRange>[2.0,)</versionRange>
                                        <goals>
                                            <goal>attach-test-resources</goal>
                                            <goal>filter-resources</goal>
                                            <goal>generate-sources</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore />
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-codegen-plugin</artifactId>
                <version>${cxf.version}</version>
                <executions>
                    <execution>
                        <!-- Note that this phase is not the usual phase to run wsdl2java...
                            this is done because DevKit requires the class be generated so it can be
                            inspected in another phase -->
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                        <configuration>
                            <wsdlOptions>
                                <wsdlOption>
                                    <!-- wsdl file path -->
                                    <wsdl>${connector.wsdl}</wsdl>
                                    <!-- pick up the WSDL from within the JAR -->
                                    <wsdlLocation>${connector.wsdlLocation}</wsdlLocation>
                                    <autoNameResolution>true</autoNameResolution>
                                    <extraargs>
                                        <!-- Package Destination -->
                                        <extraarg>-p</extraarg>
                                        <extraarg>
                                            ${connector.package}
                                        </extraarg>
                                        <!-- For DataMapper compatibility, force boolean getters and setters
                                            to follow naming convention for other getters and setters. -->
                                        <extraarg>-xjc-Xbg</extraarg>
                                        <extraarg>-xjc-Xcollection-setter-injector</extraarg>
                                    </extraargs>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <!-- Boolean getters -->
                    <dependency>
                        <groupId>org.apache.cxf.xjcplugins</groupId>
                        <artifactId>cxf-xjc-boolean</artifactId>
                        <version>${cxf.version.boolean}</version>
                    </dependency>
                    <!-- Collection Setters -->
                    <dependency>
                        <groupId>net.java.dev.vcc.thirdparty</groupId>
                        <artifactId>collection-setter-injector</artifactId>
                        <version>0.5.0-1</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5</version>
                <executions>
                    <execution>
                        <id>default-compile</id>
                        <configuration>
                            <compilerArgument>-proc:none</compilerArgument>
                            <source>${jdk.version}</source>
                            <target>${jdk.version}</target>
                        </configuration>
                    </execution>
                    <execution>
                        <id>default-testCompile</id>
                        <configuration>
                            <compilerArgument>-proc:none</compilerArgument>
                            <source>${jdk.version}</source>
                            <target>${jdk.version}</target>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.mule.tools.devkit</groupId>
                <artifactId>mule-devkit-maven-plugin</artifactId>
                <version>${mule.devkit.version}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.8</version>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <excludePackageNames>org.mule.tooling.ui.contribution:*</excludePackageNames>
                    <docletArtifact>
                        <groupId>org.mule.tools.devkit</groupId>
                        <artifactId>mule-devkit-doclet</artifactId>
                        <version>${mule.devkit.version}</version>
                    </docletArtifact>
                    <doclet>org.mule.devkit.doclet.Doclava</doclet>
                    <bootclasspath>${sun.boot.class.path}</bootclasspath>
                    <additionalparam>
                        -quiet
                        -federate JDK http://download.oracle.com/javase/6/docs/api/index.html?
                        -federationxml JDK
                        http://doclava.googlecode.com/svn/static/api/openjdk-6.xml
                        -hdf project.artifactId "${project.artifactId}"
                        -hdf project.groupId "${project.groupId}"
                        -hdf project.version "${project.version}"
                        -hdf project.name "${project.name}"
                        -hdf project.repo.name
                        "${project.distributionManagement.repository.name}"
                        -hdf project.repo.id "${project.distributionManagement.repository.id}"
                        -hdf project.repo.url
                        "${project.distributionManagement.repository.url}"
                        -hdf project.snapshotRepo.name
                        "${project.distributionManagement.snapshotRepository.name}"
                        -hdf project.snapshotRepo.id
                        "${project.distributionManagement.snapshotRepository.id}"
                        -hdf project.snapshotRepo.url
                        "${project.distributionManagement.snapshotRepository.url}"
                        -d ${project.build.directory}/apidocs
                    </additionalparam>
                    <useStandardDocletOptions>false</useStandardDocletOptions>
                    <additionalJOption>-J-Xmx1024m</additionalJOption>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>1.0-alpha-4</version>
                <executions>
                    <execution>
                        <id>enforce-maven-version</id>
                        <goals>
                            <goal>enforce</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <requireMavenVersion>
                                    <version>[3.0.0,)</version>
                                </requireMavenVersion>
                                <requireJavaVersion>
                                    <version>[1.6.0,)</version>
                                </requireJavaVersion>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>src/test/resources</directory>
            </resource>
        </resources>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.mule</groupId>
            <artifactId>mule-core</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mule.modules</groupId>
            <artifactId>mule-module-spring-config</artifactId>
            <version>${mule.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mule.tools.devkit</groupId>
            <artifactId>mule-devkit-annotations</artifactId>
            <version>${mule.devkit.version}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse</groupId>
            <artifactId>eclipse-workbench</artifactId>
            <version>3.6.1.M20100826-1330</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse</groupId>
            <artifactId>eclipse-runtime</artifactId>
            <version>3.6.0.v20100505</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>core</artifactId>
            <version>4.3.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mule.tests</groupId>
            <artifactId>mule-tests-functional</artifactId>
            <version>${mule.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mule.modules</groupId>
            <artifactId>mule-module-cxf</artifactId>
            <version>${mule.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>mulesoft-releases</id>
            <name>MuleSoft Releases Repository</name>
            <url>http://repository.mulesoft.org/releases/</url>
            <layout>default</layout>
        </repository>
        <repository>
            <id>mulesoft-snapshots</id>
            <name>MuleSoft Snapshots Repository</name>
            <url>http://repository.mulesoft.org/snapshots/</url>
            <layout>default</layout>
        </repository>
        <repository>
            <id>codehaus-releases</id>
            <name>CodeHaus Releases</name>
            <url>http://repository.codehaus.org/</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>mulesoft-plugin-releases</id>
            <name>MuleSoft Release Repository</name>
            <url>http://repository.mulesoft.org/releases/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>mulesoft-plugin-snapshots</id>
            <name>MuleSoft Snapshot Repository</name>
            <url>http://repository.mulesoft.org/snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

Step 3 Rebuilding the Project with New Dependencies

Now that your POM file includes these additions, you need to perform a clean build and install of your project.

You can run the following Maven command on the command line, from the directory where the project exists:

mvn clean install

This command invokes Maven with two goals:

  • clean tells Maven to wipe out all previous build contents

  • install tells Maven to use wsdl2java to generate the CXF client code; compile all the code for the project; run any defined tests, package the compiled code as an Eclipse update site, and install it in the local Maven repository. (Any failure during this process, such as a failed build or test, will stop Maven from attempting subsequent goals.)

See Introduction to the Build Lifecycle at the Apache Maven project web site for more details on this process.

Your preferred IDE should have support for this process as well. For example, in Eclipse you can select the project, then invoke Run as > Maven Build.

When the build is complete, you will see the files Maven generated using wsdl2java in the folder target/generated-sources/cxf.

Step 4 Adding the Generated Source Folder to the IDE Build Path

You must add the target/generated-sources/cxf folder from the previous step to the build path as recognized by your IDE.

  1. Import or re-import your Maven project to your IDE, as described in Writing Extensions in IntelliJ or "Importing a Maven Project into Eclipse/Mule Studio" in Creating a Connector Project.

  2. Look for the folder target/generated-sources/cxf.

  3. Right-click on it, then select Build Path > Use as Source Folder.

This tells your IDE that this folder by default should be treated as part of the source code.

In general, you should not modify these generated classes, because every time wsdl2java is run, these files are recreated. If the service definition changes, update the local WSDL, then run mvn clean before your next build.

Understanding the Stub Client Code Generated by WSDL2JAVA

The Java source files generated correspond to the service as described by the contents of the WSDL.

The WSDL describes a service, accessible via several ports (or endpoints). Each port supports a specific protocol and exposes a set of operations for the service. Each operation accepts and returns objects (in XML format), of types also defined in the WSDL.

The generated code from wsdl2java provides a Java stub client implementation for the web service. Classes and interfaces defined in the generated code correspond to the service, ports, operations, and types defined in the WSDL.

For this example, the most interesting generated code is:

  • SunSetRiseService class – the top level class, corresponding to the service.

  • SunSetRiseServiceSoap interface – Exposes an interface that describes the getSunSetRiseTime() method that corresponds to the operation available on the SOAP port

Once you have these, it takes only a few lines of code to call any operation on the service:

  • Instantiate the service and the port

  • Call operations against the port object, using the type classes to create arguments and responses as Java objects

CXF and JAX-WS Web Service Annotations

The generated stub client code makes extensive use of JAX-WS annotations, and can thus be a bit difficult to decipher completely. Fortunately, you do not need to understand the details of this generated code to use it. See the Apache CXF: Developing a Service documentation for details about the individual annotations used.

Also important is class LatLonDate the entity class that defines the object used to pass latitude/longitude/date data to and return it from the getSunSetRiseTime() operation.

Creating the SOAP Proxy Class

Now, build the proxy class that calls the stub client. This class is produced by hand-coding; DevKit does not generate any of this for you.

Creating the Proxy Client Class Definition

Here you create a class of your own – for this example, in package org.tutorial.sunsetrise.client, create class SunSetRiseProxyClient.

First, add the following imports:

import java.net.URL;
import org.mule.api.ConnectionException;
import org.mule.api.ConnectionExceptionCode;
import org.tutorial.sunsetrise.definition.SunSetRiseService;
import org.tutorial.sunsetrise.definition.SunSetRiseServiceSoap;
import org.tutorial.sunsetrise.definition.LatLonDate;

Then, add the following code to the class definition, that creates the service and port instances:

public class SunSetRiseProxyClient {

        private SunSetRiseServiceSoap port;

        public SunSetRiseProxyClient() {}

        public void initialize() throws ConnectionException {
            SunSetRiseService svc;
            // pick up the WSDL from the location in the JAR
            URL url= SunSetRiseService.class.getClassLoader().getResource("wsdl/sunsetriseservice.wsdl");
            svc = new SunSetRiseService(url);

            port = svc.getSunSetRiseServiceSoap();

            // Configure Authentication headers here, if the service uses them.
            // Add parameters as needed to initialize() to pass them in from connector
        }

/* operations will be added here */
}

The initialize() method, which creates the port instance used to call methods on the stub client, is ultimately called from the @Connect method of the @Connector class.

Authentication in the Proxy Client Class

This example does not include any authentication. The API at WebserviceX.net used in this sample does not require authentication. It does use the connection management annotations described in Implementing Connection Management, which provide for multitenancy support.

In a connector of this style that does support authentication, the proxy class is responsible for providing any authentication-related logic that wraps around the CXF stub class. For example, the proxy client class may have to add headers or additional URL parameters to the request, to pass any tokens or credentials. The @Connector class should have properties that hold credentials that are then passed to the proxy client instance.

The different authentication methods are discussed in Authentication and Connection Management; find your authentication method and refer to the examples for guidance on how to add authentication handling in the proxy client.

Preparing the @Connector Class

The main @Connector class wraps the client logic class created in the previous step and includes the annotations needed for a Mule Connector. It defines the methods for operations that your connector will expose in Mule.

The skeleton @Connector class created from the DevKit Maven archetype is the starting point for this work.

sunsetriseConnector.java – as generated by DevKit

/**
 * This file was automatically generated by the Mule Development Kit
 */
package org.tutorial.sunsetrise;
import org.mule.api.annotations.Connector;
import org.mule.api.annotations.Connect;
import org.mule.api.annotations.ValidateConnection;
import org.mule.api.annotations.ConnectionIdentifier;
import org.mule.api.annotations.Disconnect;
import org.mule.api.annotations.param.ConnectionKey;
import org.mule.api.ConnectionException;
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Processor;
/**
 * Cloud Connector
 *
 * @author MuleSoft, Inc.
 */
@Connector(name="sunsetrise", schemaVersion="1.0-SNAPSHOT")
public class SunsetriseConnector
{
    /**
     * Configurable
     */
    @Configurable
    private String myProperty;
    /**
     * Set property
     *
     * @param myProperty My property
     */
    public void setMyProperty(String myProperty)
    {
        this.myProperty = myProperty;
    }
    /**
     * Get property
     */
    public String getMyProperty()
    {
        return this.myProperty;
    }
    /**
     * Connect
     *
     * @param username A username
     * @param password A password
     * @throws ConnectionException
     */
    @Connect
    public void connect(@ConnectionKey String username, String password)
        throws ConnectionException {
        /**
         * CODE FOR ESTABLISHING A CONNECTION GOES IN HERE
         */
    }
    /**
     * Disconnect
     */
    @Disconnect
    public void disconnect() {
        /**
         * CODE FOR CLOSING A CONNECTION GOES IN HERE
         */
    }
    /**
     * Are we connected
     */
    @ValidateConnection
    public boolean isConnected() {
        return true;
    }
    /**
     * Are we connected
     */
    @ConnectionIdentifier
    public String connectionId() {
        return "001";
    }
    /**
     * Custom processor
     *
     * {@sample.xml ../../../doc/sunsetrise-connector.xml.sample sunsetrise:my-processor}
     *
     * @param content Content to be processed
     * @return Some string
     */
    @Processor
    public String myProcessor(String content)
    {
        /**
         * MESSAGE PROCESSOR CODE GOES HERE
         */
        return content;
    }
}

Make the following changes to the @Connector class skeleton to link the @Connector class to the underlying proxy class and add connection management support.

  • Import the the proxy client class definition:

    import org.tutorial.sunsetrise.client.SunSetRiseProxyClient;
  • Add to the class a property that holds the instance of the connector class:

    public class SunSetRiseConnector
    {
        private SunSetRiseProxyClient client;
    
    ...
  • To support connection management, add the @Connect, @Disconnect, @ValidateConnection and @ConnectionIdentifier methods inside the class definition, as well as the @ConnectionKey, as shown:

/**
     * Connect
     *
     * @param username A username
     * @param password A password. (Ignored, for this connector.)
     * @throws ConnectionException
     */
    @Connect
    public void connect(@ConnectionKey String username, String password)
        throws ConnectionException {
        /**
         * "Establish connection" here =
         * "create proxy client and port for later method calls"
         */

        client = new SunSetRiseProxyClient();
        client.initialize();
    }
    /**
     * Disconnect
     */
    @Disconnect
    public void disconnect() {
        client=null;
    }
    /**
     * Are we connected
     */
    @ValidateConnection
    public boolean isConnected() {
        return (client!=null);
    }
    /**
     * Connection Identifier
     */
    @ConnectionIdentifier
    public String connectionId() {
        return "SunSetRise-";
    }

Note that the @Connect method here instantiates and initializes the port from the proxy client the first time connect() is called, and saves the proxy client instance in client.

You may need to add the client class as a variable of the connector. For example:

SunSetRiseProxyClient client = null ;

Adding an Operation to the Connector

Adding an operation to the connector requires the following steps:

  • Import any entity classes referenced in the operation

  • Add a method for the operation in the proxy class that calls the stub client

  • Add a @Processor method in the @Connector class that calls the new proxy class method

  • Add any required Javadoc (including XML snippets) to the @Processor method

You may also have to add @Configurable properties to the connector, depending on your situation.

Finally, you should add unit tests to validate the behavior of the operation on a variety of inputs and failure situations.

Apply a Test-Driven Approach

When it comes to adding operations to your connector, successful projects generally follow a cycle similar to test-driven development:

  • Identify detailed requirements for the operation-- entities (POJOs or Maps with specific content) it can accept as inputs or return as responses, how those responses translate into outputs, and what exceptions it may raise;

  • Implement JUnit tests that cover some of those requirements;

  • Implement enough of your operation to pass those tests, including creating new entity classes and exceptions;

  • Update your @Connector class and other code with the comments that populate the Javadoc related to the operation

Iterate until you cover all the scenarios covered in your requirements for a given operation. Then use the same cycle again to implement the rest of your operations, until your connector functionality is complete.

You may ask, "When do I try my connector in Studio?" It is useful, as well as gratifying, to manually test each operation as you go, in addition to the automated JUnit tests:

  • You get to see basic operation functionality in action as you work on it, which gives you a sense of progress

  • You get to see how the connector appears in the Studio UI, something the automated unit tests cannot show you. For example, text from the Javadoc comments is used to populate tooltips for the fields in the dialog boxes in the connector.

This will provide the opportunity to polish the appearance of the connector, improve the experience with sensible defaults, and so on.

However, this does not take away from the value of the test-driven approach. Many connector development projects have bogged down or have produced hard-to-use connectors because of a failure to define tests as you define the operations – it seems like (and is) more work up front, but it does pay off – you get to a better result, faster.

Adding a Proxy Class Method for the Operation

For each operation you plan to expose on the final connector, add a method to the proxy class that calls the corresponding method on the stub client. The stub client exposes all methods described in the WSDL; if you do not want to expose all operations of the service in your connector, simply omit the unneeded operations from the proxy client and @Connector class.

For this example, update class SunSetRiseProxyClient to expose the getSunSetRiseTime() operation, which uses instances of org.tutorial.sunsetrise.definition.LatLonDate as both parameter and return value. Import LatLonDate into the proxy class definition.

// add to imports
import org.tutorial.sunsetrise.definition.LatLonDate;

....


// Add proxy class method for getSunSetRiseTime() operation

        public LatLonDate getSunSetRiseTime(LatLonDate in) {
            // one could do some pre-call validation here on the input parameter etc.
            return port.getSunSetRiseTime(in);
        }

The complete code for SunSetRiseProxyClient is here:

Show source

package org.tutorial.sunsetrise.client;
import java.net.MalformedURLException;
import java.net.URL;
import org.mule.api.ConnectionException;
import org.mule.api.ConnectionExceptionCode;
import org.tutorial.sunsetrise.definition.SunSetRiseService;
import org.tutorial.sunsetrise.definition.SunSetRiseServiceSoap;
import org.tutorial.sunsetrise.definition.LatLonDate;
public class SunSetRiseProxyClient {

        private SunSetRiseServiceSoap port;

        public SunSetRiseProxyClient() {}

        public void initialize(String wsdlLocation) throws ConnectionException {
            SunSetRiseService svc;

            try {
                svc = new SunSetRiseService(new URL(wsdlLocation));
            } catch (MalformedURLException e) {
                // This is an Exception used by Mule at Connection Time
                throw new ConnectionException(ConnectionExceptionCode.UNKNOWN,
                     "", "The URL of the WSDL location is malformed");
            }

            port = svc.getSunSetRiseServiceSoap();

            // In here, configure Authentication headers if the service uses them.

        }
        public LatLonDate getSunSetRiseTime(LatLonDate in) {
            return port.getSunSetRiseTime(in);
        }

}

Adding @Processor Method to @Connector Class

In the @Connector class, you must:

  • Import any entity classes needed for the operation

  • Add a @Processor method for the operation that calls the operation’s method on the proxy client class

For this example, import the LatLonDate class:

import org.tutorial.sunsetrise.definition.LatLonDate;

Then add the getSunSetRiseTime() method as shown below:

/**
     * Custom processor
     *
     * {@sample.xml ../../../doc/sunsetrise-connector.xml.sample sunsetrise-connector:get-sun-set-rise-time}
     *
     * @param in A LatLonDate object, with latitude, longitude, month, date, and year initialized. Defaults to the payload.
     * @return Latitude, Longitude, Date, Sunrise and Sunset times, and a Timezone value in a LatLonDate
     */
    @Processor
    public LatLonDate getSunSetRiseTime(@Optional @Default("#[payload]") LatLonDate in)
    {
        return client.getSunSetRiseTime(in);
    }

Things to notice:

  • The use of the @Optional and @Default annotations. These specify that the operation should take the payload as its argument, if no argument is specified.

The parameters to the @Processor method are automatically exposed in the property dialog for the connector as operation parameters, with the tooltips determined by the corresponding @param comments.

Adding XML Configuration Examples for JavaDoc

DevKit enforces JavaDoc documentation of your methods. One of the things you must add is an XML sample of the inputs required by each connector method. Learn more about JavaDoc annotations for DevKit.

In the @Connector class source code, the following comment text links the method to its required XML sample:

* {@sample.xml ../../../doc/sunsetrise-connector.xml.sample sunsetrise-connector:get-sun-set-rise-time}

You will see the sample code snippets file in the doc folder in the DevKit generated project.

DevKit created this file, but you need to populate it with example Mule XML configurations for each operation. For this example, add the following to the file to document the getSunSetRiseTime() operation:

<!-- BEGIN_INCLUDE(sunsetrise-connector:get-sun-set-rise-time) -->
<sunsetrise:get-sun-set-rise-time latitude="40.4" longitude="32.25" month="7" day="12" year="2013" />
<!-- END_INCLUDE(sunsetrise-connector:get-sun-set-rise-time) -->

When you build the JavaDoc, the sample above is inserted into the documentation.

See Creating DevKit Connector Documentation for full information on filling in the JavaDoc for your connector.

Putting it All Together

You can build and test your connector once you have completed at least the following tasks:

  • Created the stub client with wsdl2java and maven

  • Created the proxy client class with an initialize method and at least one operation

  • Added a @Processor method on the @Connector class that calls the operation

  • Provided the required documentation and unit tests

See Installing and Testing Your Connector for the steps to follow to build your connector and import it into Studio.

Once you complete this process, you will see the SunSetRise connector in the palette.

You can build a simple flow to demo the connector, as follows:

<mule xmlns:sunsetrise="http://www.mulesoft.org/schema/mule/sunsetrise" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="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-current.xsd
    http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
    http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
    http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
    http://www.mulesoft.org/schema/mule/sunsetrise http://www.mulesoft.org/schema/mule/sunsetrise/1.0-SNAPSHOT/mule-sunsetrise.xsd">
    <sunsetrise:config name="SunSetRise_Service" username="foo" doc:name="SunSetRise Service">
        <sunsetrise:connection-pooling-profile initialisationPolicy="INITIALISE_ONE" exhaustedAction="WHEN_EXHAUSTED_GROW"/>
        <reconnect/>
    </sunsetrise:config>
    <flow name="SunRiseFlowFlow1" doc:name="SunRiseFlowFlow1">
        <http:inbound-endpoint exchange-pattern="request-response" host="localhost" port="8081" doc:name="HTTP" path="demoflow"/>
        <sunsetrise:get-sun-set-rise-time config-ref="SunSetRise_Service" doc:name="SunSetRise Service">
            <sunsetrise:in latitude="15" longitude="0" timeZone="0" day="12" month="12" year="2013"/>
        </sunsetrise:get-sun-set-rise-time>
        <json:object-to-json-transformer doc:name="Object to JSON"/>
        <http:response-builder status="200" contentType="application/json" doc:name="HTTP Response Builder">
            <http:cache-control noCache="true" noStore="true"/>
        </http:response-builder>
    </flow>
</mule>

Next Steps

Once you get through the process above, you have a working SOAP CXF connector. You can: