Using Maven and JAX-WS to call Salesforce’s API

I have setup for my own usage a development platform using Maven with Artifactory in order to speed up and ease my development process. In order to validate this platform, i decided to call a Web Service exposed by Salesforce using their enterprise WSDL. I have notice that some people have issue in implementing jax-ws due to name collision’s issue like this:

[ERROR] A class/interface with the same name "com.sforce.soap.enterprise.DescribeGlobalTheme" is already in use. Use a class customization to resolve this conflict.
  line 8686 of file:/C:/eclipse_workspace/juno/sfdc-jaxb-api-enterprise-wsdl/src/main/resources/enterprise.wsdl
[ERROR] (Relevant to above error) another "DescribeGlobalTheme" is generated from here.
  line 7932 of file:/C:/eclipse_workspace/juno/sfdc-jaxb-api-enterprise-wsdl/src/main/resources/enterprise.wsdl
[ERROR] A class/interface with the same name "com.sforce.soap.enterprise.DescribeApprovalLayout" is already in use. Use a class customization to resolve this conflict.
  line 8828 of file:/C:/eclipse_workspace/juno/sfdc-jaxb-api-enterprise-wsdl/src/main/resources/enterprise.wsdl
[ERROR] (Relevant to above error) another "DescribeApprovalLayout" is generated from here.
  line 8235 of file:/C:/eclipse_workspace/juno/sfdc-jaxb-api-enterprise-wsdl/src/main/resources/enterprise.wsdl
[ERROR] A class/interface with the same name "com.sforce.soap.enterprise.DescribeLayout" is already in use. Use a class customization to resolve this conflict.
  line 8794 of file:/C:/eclipse_workspace/juno/sfdc-jaxb-api-enterprise-wsdl/src/main/resources/enterprise.wsdl
[ERROR] (Relevant to above error) another "DescribeLayout" is generated from here.
  line 8253 of file:/C:/eclipse_workspace/juno/sfdc-jaxb-api-enterprise-wsdl/src/main/resources/enterprise.wsdl
[ERROR] Two declarations cause a collision in the ObjectFactory class.
  line 8686 of file:/C:/eclipse_workspace/juno/sfdc-jaxb-api-enterprise-wsdl/src/main/resources/enterprise.wsdl  

To avoid this compilation issue with jax-ws, you use a class customization defined in bindings file. Here is the file that i’m using:

<bindings    
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://java.sun.com/xml/ns/jaxws">
  <bindings
    node="//xsd:schema[@targetNamespace='urn:enterprise.soap.sforce.com']">
    <jaxb:globalBindings
      underscoreBinding="asCharInWord" />
    <jaxb:schemaBindings>
      <jaxb:nameXmlTransform>
        <jaxb:typeName
          suffix="Type" />
      </jaxb:nameXmlTransform>
    </jaxb:schemaBindings>
  </bindings>
  <enableWrapperStyle>false</enableWrapperStyle>
  <enableAsyncMapping>false</enableAsyncMapping>
</bindings>  

A sample usage of this API is shown below. It use compression when calling Salesforce’s WS:

package com.salesforce;

import com.sforce.soap.enterprise.*;
import com.sun.xml.ws.developer.WSBindingProvider;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.BindingProvider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import com.sun.xml.bind.api.JAXBRIContext;
import com.sun.xml.ws.api.message.Headers;

public class MainTesting {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Logger logger = Logger.getLogger("org.salesforce.quickstart");
		// You should maintain this object until the end of the Java program, in
		// order to make sub-sequent calls after the login operation.
		LoginResponse loginResponse = null;
		Soap port = null;
		URL wsdlLocation = null;
		try {
			wsdlLocation = MainTesting.class.getClassLoader().getResource(
					"enterprise.wsdl");
			if (wsdlLocation == null) {
				logger.log(Level.INFO, "enterprise.wsdl not found");
				return;
			}
			port = new SforceService(wsdlLocation, new QName(
					"urn:enterprise.soap.sforce.com", "SforceService"))
					.getSoap();

			// Create the login object
			Login login = new Login();
			login.setUsername("YOUR_EMAIL");
			login.setPassword("YOUR_PASSWORD concatenate YOUR SECURITY_TOKEN");

			loginResponse = port.login(login);
			logger.log(Level.INFO, loginResponse.toString());
			logger.log(Level.INFO, "SessionId="
					+ loginResponse.getResult().getSessionId() + ", userId="
					+ loginResponse.getResult().getUserId()
					+ " , and optimize call by activating compression");
			// on a successful login, you should always set up your session id
			// and the url for subsequent calls, and enable compression when use
			// salesforce infra
			WSBindingProvider bindingProvider = ((WSBindingProvider) port);
			bindingProvider.getRequestContext().put(
					BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
					loginResponse.getResult().getServerUrl());

			// Enable GZip compression
			Map<String, List<String>> httpHeaders = new HashMap<String, List<String>>();
			httpHeaders.put("Content-Encoding",
					Collections.singletonList("gzip"));
			httpHeaders.put("Accept-Encoding",
					Collections.singletonList("gzip"));
			Map<String, Object> reqContext = bindingProvider
					.getRequestContext();
			reqContext.put(MessageContext.HTTP_REQUEST_HEADERS, httpHeaders);

			SessionHeader sh = new SessionHeader();
			sh.setSessionId(loginResponse.getResult().getSessionId());
			JAXBContext jaxbContext = null;
			try {
				jaxbContext = JAXBContext
						.newInstance("com.sforce.soap.enterprise");
				bindingProvider.setOutboundHeaders(Headers.create(
						(JAXBRIContext) jaxbContext, sh));
			} catch (JAXBException e) {
				logger.log(Level.INFO, "JAXBException :" + e.getMessage());
			}
			
			// check to see if we are already logged in
	        if (loginResponse != null) {
				Search search = new Search();
				search.setSearchString("FIND {Chenda Mok}");				
				SearchResponse searchResponse = port.search(search);
				logger.log(Level.INFO, "Searching 'Chenda Mok': " + searchResponse.toString());
				
	        }
		} catch (Exception e) {
			logger.log(Level.INFO, "Failed with exception:" + e.getMessage());
		}
	}
}
  

Below my pom.xml to build the jaxb classes from WSDL:

<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>com.salesforce</groupId>
	<artifactId>sfdc-jaxb-api-enterprise-wsdl</artifactId>
	<version>0.0.1-SNAPSHOT</version>


	<name>sfdc-api-wsdl</name>
	<url>http://maven.apache.org</url>

	<distributionManagement>
		<repository>
			<id>internal</id>
			<name>internal</name>
			<url>http://localhost:8081/artifactory/internal</url>
		</repository>
		<snapshotRepository>
			<id>snapshot</id>
			<name>snapshot</name>
			<url>http://localhost:8081/artifactory/snapshot</url>
		</snapshotRepository>
	</distributionManagement>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.jvnet.jaxb2_commons</groupId>
			<artifactId>jaxb2-basics</artifactId>
			<version>0.5.1</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.6</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.jvnet.jax-ws-commons</groupId>
				<artifactId>jaxws-maven-plugin</artifactId>
				<version>2.3</version>
				<dependencies>
					<dependency>
						<groupId>org.jvnet.jaxb2_commons</groupId>
						<artifactId>jaxb2-basics</artifactId>
						<version>0.5.1</version>
					</dependency>
				</dependencies>
				<configuration>
					<implDestDir>target/generated-sources</implDestDir>
					<sourceDestDir>target/generated-sources</sourceDestDir>
					<extension>true</extension>
					<protocol>Xsoap1.1</protocol>
					<target>2.0</target>
					<xjcArgs>
						<xjcArg>-target</xjcArg>
						<xjcArg>2.0</xjcArg>
						<xjcArg>-XtoString</xjcArg>
						<xjcArg>-Xequals</xjcArg>
						<xjcArg>-XhashCode</xjcArg>
						<!-- you can use this argument if you don't want to bother with binding 
							file -->
						<!-- <xjcArg>-XautoNameResolution</xjcArg> -->
					</xjcArgs>
					<wsdlDirectory>src/main/resources/</wsdlDirectory>
					<bindingDirectory>src/main/resources/bindings</bindingDirectory>
					<bindingFiles>
						<bindingFile>bindings.xjb</bindingFile>
					</bindingFiles>
				</configuration>
				<executions>
					<execution>
						<id>compile-wsdl-sfdc</id>
						<phase>generate-sources</phase>
						<goals>
							<goal>wsimport</goal>
						</goals>
						<configuration>
							<wsdlFiles>
								<wsdlFile>enterprise.wsdl</wsdlFile>
							</wsdlFiles>
							<wsdlLocation>src/main/resources/</wsdlLocation>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
		<pluginManagement>
			<plugins>
				<!--This plugin's configuration is used to store Eclipse m2e settings 
					only. It has no influence on the Maven build itself. -->
				<plugin>
					<groupId>org.eclipse.m2e</groupId>
					<artifactId>lifecycle-mapping</artifactId>
					<version>1.0.0</version>
					<configuration>
						<lifecycleMappingMetadata>
							<pluginExecutions>
								<pluginExecution>
									<pluginExecutionFilter>
										<groupId>
											org.jvnet.jax-ws-commons
										</groupId>
										<artifactId>
											jaxws-maven-plugin
										</artifactId>
										<versionRange>
											[2.3,)
										</versionRange>
										<goals>
											<goal>wsimport</goal>
										</goals>
									</pluginExecutionFilter>
									<action>
										<ignore></ignore>
									</action>
								</pluginExecution>
							</pluginExecutions>
						</lifecycleMappingMetadata>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>  

Launch a mvn deploy to deploy your JAR in the artifactory’s repository.

SFDC-api-wsdl-artifactory

Thanks for reading.

Advertisements

About Chenda Mok

19 years of hands on experience in software design and development with emphasis on Enterprise Application Integration (EAI), Services Oriented Architecture (SOA) and Identity Management (IDM) solutions. I’m a software engineer, member of the professional service delivery team working for Salesforce. Prior to this, I worked for Oracle as Solution Architect, through SeeBeyond(06/2005), then SUN’s acquisition (04/2009). After my master’s degree in computer science in 1997; I always delivered consulting on architecture, design, implementation on integration’s field. I’m interested in architecture using EAI/SOA/IDM/BPM/Cloud technologies, software development and Java’s related technologies. I may blog about my work/activities at Salesforce, but I do not speak for my employer, past, present or future.
This entry was posted in Artifactory, Maven and tagged , , , . Bookmark the permalink.