Contact Us 1-800-596-4880

Creating a Connector for a SOAP Service Via CXF Client

DevKit is compatible only with Studio 6 and Mule 3. To build Mule 4 connectors, see the Mule SDK documentation.

This example demonstrates how 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. For more details on the full range of possible client models using CXF, see the Apache CXF documentation for building clients, especially Developing a Consumer with 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

6 package

Prerequisites

This document assumes you are familiar with SOAP Web Services, including WSDL definitions. (You can get important concepts and terms on WSDL from the WSDL specifications at the W3C web site.) It further assumes you have some understanding of CXF and wsdl2java and how they can be used to build Web Service clients. (For background on these topics, see Apache CXF: How do I develop a client? and especially Apache CXF: Developing a Consumer with CXF.) This document also assumes you have created a Maven project for a connector as described in Creating an Anypoint Connector Project.

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):

  • Obtain 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 final architecture looks like the image below.

create connector architecture

The Example Service: SunSetRiseService

The web service for the demo is the SunSetRise web service. See SunSetRise WSDL.

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

  • Your location in latitude and longitude. Note that you need use negative latitude values for the Southern Hemisphere and negative longitude values for the Eastern Hemisphere. Coordinates are in decimal rather than degrees, minutes and seconds

  • A target date for providing sunrise and sunset times

  • The time zone of the final results (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 includes them 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>

Note that 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, which 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 becomes the core of your connector.

The sections below describe the steps to create the stub client and add it to your project.

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 from http://www.webservicex.net/sunsetriseservice.asmx?WSDL.

See Setting Up Your API Access 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 called 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 the POM

The first block of code adds several properties to your POM. These 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 do 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 generates 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 do not have to edit this block, just add it.

Wsdl2Java POM Update:
<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
	 that the class be generated so it can be used in
	 the generate-sources phase by DevKit. DevKit generates
	 code from annotations etc and 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 the
		     boolean getters and setters follow naming conventions
		     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>

Notes:

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

  • The xjc-Xbg argument is included to enable 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>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>
                                        <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, stops Maven from attempting subsequent goals.)

For more details on this process, see Introduction to the Build Lifecycle at the Apache Maven project.

Your preferred IDE should include 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 finds the files generated by Maven using wsdl2java in the folder target/generated-sources/cxf.

folder structure

Adding the Generated Source Folder to the IDE Build Path

If the target/generate-sources/cxf source folder generated in the previous step is not present in your build path, follow the steps below.

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 "Importing a Maven Project into Eclipse/Mule Studio" in Creating an Anypoint Connector Project.

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

  3. Right-click the folder name, then select Build Path > Use as Source Folder.

    SOAP1

This tells your IDE that this folder should by default 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, which 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. For details about the individual annotations used, see Apache CXF: Developing a Service.

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
        }

/* Add operations 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 which provide for multitenancy support.

In a connector 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 Methods; 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 exposes 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 DevKit
 */
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 below.

/**
     * 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

Based on MuleSoft experience, most successful connector implementation projects follow a cycle similar to test-driven development when building out operations on a connector:

  • Determine detailed requirements for the operation – entities (POJOs or Maps with specific content) that it can accept as input or return as responses; any edge cases like invalid values, values of the wrong type, and so on; and what exceptions the operation may raise

  • Implement JUnit tests that cover 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 to implement each operation, until your connector functionality is complete.

If your client library is well-documented, the expected behaviors for operations should be clear, and you may be able to get away with less unit testing for edge cases and certain exceptional situations – but bear in mind that your connector is only as reliable as the Java client you based it on.

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. Testing each operation allows you to:

  • See basic operation functionality in action as you work on it, which gives you a sense of progress

  • 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

Manual testing provides the opportunity to polish the appearance of the connector, improve the experience with sensible defaults, and so on.

However, this does not diminish the value of the test-driven approach. Many connector development projects have bogged down or produced hard-to-use connectors because of a failure to define tests as you define the operations, which it seems like (and is) more work up front, but does pay off – you get 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 can do pre-call validation here on the input parameter etc.
  return port.getSunSetRiseTime(in);
}
Complete code for SunSetRiseProxyClient is:
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();

            // 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);
}

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

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 can 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

Once you complete this process, you can locate the SunSetRise connector in the palette.

You can build a simple flow to demo the connector, as shown below.

Studio Visual Editor

studio config 2
studio config

XML Editor

<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" doc:name="SunSetRise">
            <sunsetrise:in latitude="15" longitude="0" timeZone="0" day="12" month="12" year="2014" sunRiseTime="0.0" sunSetTime="0.0"/>
        </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: