Training „git“
07.10.2010 - 08.10.2010, Essen
Training „Eclipse RCP“
28.03.2011 - 01.04.2011, Dortmund
09.10.2009

Connecting to Campaign Monitor using Java, Spring, JAX-WS and Maven

This is a recipe for calling the Campaign Monitor Web Service API from a Java/Spring application that is built with Maven.

Generating client classes

First step is to generate client classes from the WSDL file. This task can be left to the jaxws-maven-plugin. It suffices to copy the wsdl file to a dedicated web service definition folder and configure the plug-in to use this folder:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxws-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>wsimport</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <wsdlDirectory>${basedir}/src/main/java/de/ralfebert/webservices</wsdlDirectory>
                <bindingDirectory>${basedir}/src/main/java/de/ralfebert/webservices</bindingDirectory>
            </configuration>
        </plugin>
    </plugins>
</build>

This generates the client classes automatically under target/jaxws/wsimport/java when the project is built. You can trigger the generation manually using:

mvn generate-sources

Customizing the client classes

Unfortunately, the generation yields a naming conflict for one of the wsdl types:

[ERROR] A class/interface with the same name "com.createsend.api.api.SubscriberUnsubscribe"
        is already in use. Use a class customization to resolve this conflict.
  line 92 of src/main/java/de/ralfebert/webservices/wsdl/campaignmonitor.wsdl

[ERROR] (Relevant to above error) another "SubscriberUnsubscribe" is generated from here.
  line 139 of src/main/java/de/ralfebert/webservices/wsdl/campaignmonitor.wsdl

[ERROR] Two declarations cause a collision in the ObjectFactory class.
  line 139 of src/main/java/de/ralfebert/webservices/wsdl/campaignmonitor.wsdl

Also, I wasn’t happy with the package and class name which were automatically generated from the schema URL http://api.createsend.com/api/ and the service name api.

These things can be customized using a binding file wsdl-customization.xml:

<?xml version="1.0" encoding="utf-8"?>
<jaxws:bindings
    wsdlLocation="campaignmonitor.wsdl"
    xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/jaxws http://java.sun.com/xml/ns/jaxws/wsdl_customizationschema_2_0.xsd
        http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd">

    <jaxws:package name="com.campaignmonitor.services" />

    <jaxws:bindings node="//wsdl:portType[@name='apiSoap']">
        <jaxws:class name="CampaignMonitorService" />
    </jaxws:bindings>

     <jaxws:bindings node="//xs:schema[@targetNamespace='http://api.createsend.com/api/']">
        <jaxws:bindings node="xs:complexType[@name='SubscriberUnsubscribe']">
            <jaxb:class name="SubscriberUnsubscribeType"/>
        </jaxws:bindings>
        <jaxb:schemaBindings>
            <jaxb:package name="com.campaignmonitor.types"/>
        </jaxb:schemaBindings>
    </jaxws:bindings>

</jaxws:bindings>

Configuring the service as Spring Bean

The Spring framework provides a factory class JaxWsPortProxyFactoryBean to configure an instance of the generated interface CampaignMonitorService:

<bean id="campaignMonitorService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
    <property name="serviceInterface" value="com.campaignmonitor.services.CampaignMonitorService" />
    <property name="wsdlDocumentUrl" value="classpath:de/ralfebert/webservices/campaignmonitor.wsdl" />
    <property name="namespaceUri" value="http://api.createsend.com/api/" />
    <property name="serviceName" value="api" />
    <property name="endpointAddress" value="https://api.createsend.com/api/api.asmx" />
</bean>

Thanks to Gunnar Morling for giving an example in his blog: Writing and testing JAX-WS clients using the Spring framework

Writing a newsletter service

Unfortunately the Campaign Monitor API is rather rudimentary - most types are simple Strings or Objects, return codes are used instead of SOAP faults. So I wrapped the needed web service calls in a convenient NewsletterService which handles subscribing and unsubscribing to one subscriber list and can easily be extended with other operations:

@Service
public class NewsletterService {

    private static final Logger log = LoggerFactory.getLogger(NewsletterService.class);

    @Autowired
    private CampaignMonitorService campaignMonitorService;
    private String apiKey;
    private String listId;

    public void subscribeNewsletter(String email) {
        SubscriberAddAndResubscribe request = new SubscriberAddAndResubscribe();
        request.setApiKey(apiKey);
        request.setListID(listId);
        request.setName("");
        request.setEmail(email);
        Result r = campaignMonitorService.addAndResubscribe(request).getSubscriberAddAndResubscribeResult();

        if (r.getCode() != 0) {
            throw new RuntimeException(r.getCode() + " " + r.getMessage());
        }
    }

    public void unsubscribeNewsletter(String email) {
        SubscriberUnsubscribe unsubscribe = new SubscriberUnsubscribe();
        unsubscribe.setApiKey(apiKey);
        unsubscribe.setListID(listId);
        unsubscribe.setEmail(email);
        Result r = campaignMonitorService.unsubscribe(unsubscribe).getSubscriberUnsubscribeResult();

        if (r.getCode() == 202) {
            // IGNORE: Subscriber not in list or has already been removed
        } else if (r.getCode() != 0) {
            throw new RuntimeException(r.getCode() + " " + r.getMessage());
        }
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }

    public void setListId(String listId) {
        this.listId = listId;
    }

}

This is configured as a Spring bean and is used by other services that need to talk to the Campaign Monitor services:

<bean id="newsletterService" class="de.ralfebert.core.company.service.NewsletterService">
    <property name="apiKey" value="${campaignMonitor.apiKey}"/>
    <property name="listId" value="${campaignMonitor.listId}"/>
</bean>

This recipe is licensed under CC-BY-SA, the code examples are licensed under the BSD license.

I'm looking forward to your comments:

Schulungen

Java

Ralf Ebert | Blog | Java | Connecting to Campaign Monitor using Java, Spring, JAX-WS and Maven