Creating Portlets for Integration With Salesforce.com

Part 2: Making Calls Against the Web Service

Integration with the Salesforce.com API is a feature that is highly desirable for any organization which uses that platform to track opportunities in the sales pipeline; as a proof of concept, we recently went through the process of developing several portlets for deployment on Liferay which integrate with the Salesforce.com Web Services. This is a multi-part blog post detailing the challenges that we faced in completing this task; the second entry will run through the details of how to make calls against the Salesforce.com web services endpoint.

Overview

Once you’ve configured your project to generate and compile code based on the Salesforce.com enterprise WSDL, the next tricky part is handling the login/authentication process itself. There are three authentication components of note when logging in to the Salesforce.com web services endpoint; a username, a password, and a security token. The username and password are the ones that a user uses to log in to the salesforce.com website; the security token is required when logging in to the web service, and is tied to the user’s password. It can be generated or re-generated via the user’s profile on the salesforce.com website.

The basic binding object through which all salesforce.com web service calls are performed is the generated class com.sforce.soap.enterprise.Soap. A web service call to salesforce.com is executed in the following manner:

  1. Create a Soap object instance using the SforceService class
  2. Log in to the web service endpoint using authentication credentials
  3. Set the endpoint address on the Soap instance’s request context.
  4. Retrieve the session ID from the login attempt
  5. Perform queries against the web service via the Soap proxy object, passing in a session header with the stored ID
  6. Complete the web service session and log out via the Soap proxy object.

Seems simple, right?

Logging in to the Salesforce.com Web Services Endpoint

In order to streamline this process, we broke out the login and logout process into separate methods in our salesforce.com framework. Most of the tricky stuff in the authentication process is handled by the login() method:

private static final String WSDL_CLASSPATH_LOCATION = "wsdl/enterprise.wsdl";

protected Soap binding;
protected SessionHeader sessionHeader;

protected void login() {
	//This is needed in order to correctly initialize the web service in a 
	// machine-independent fashion.
	URL wsdlUrl = SforceService.class.getClassLoader().getResource(WSDL_CLASSPATH_LOCATION);
	
	SforceService service = new SforceService(wsdlUrl);
	binding = service.getPort(Soap.class);

	LoginResult loginResult;
        sessionHeader = new SessionHeader();
	try {
		loginResult = binding.login(getSforceUsername(), getSforcePassword(), null);
		Map<string, object=""> requestContext = ((BindingProvider)binding).getRequestContext();
		requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, loginResult.getServerUrl());
	        sessionHeader.setSessionId(loginResult.getSessionId());
	} catch (InvalidIdFault e) {
		logger.error("Invalid ID [" + getSforceUsername() 
			+ "] while attempting to log in to web service.", e);
		throw new CommunicationException(e);
	} catch (LoginFault e) {
		logger.error("Login error occurred while attempting to log in to web service.", e);
		throw new InvalidCredentialsException(e);
	} catch (UnexpectedErrorFault e) {
		logger.error("Unexpected error occurred while attempting to log in to web service.", e);
		throw new CommunicationException(e);
	}
}

Let’s take a look at what’s going on here. First off, we create an instance of the SforceService class using the enterprise WSDL bundled in the JAR file. Building the URL via the classloader is critical, as we want to ship the WDSL within the JAR itself to avoid configuration issues.

URL wsdlUrl = SforceService.class.getClassLoader().getResource(WSDL_CLASSPATH_LOCATION);

SforceService service = new SforceService(wsdlUrl);

Next up, we retrieve the Soap binding instance stored in the SforceService instance and assign it to an instance variable. This object is the one that we will use in other methods to send requests to the web service endpoint.

binding = service.getPort(Soap.class);

Finally, we send the actual login request to the web service. This request returns a LoginResult, an object which contains various information used to send additional requests to the service using the same session, so it’s important that we retrieve the information needed and store it somewhere for later use. In our example, we retrieve two pieces of information: the web service endpoint address and the session ID. We store the web service endpoint in the binding object’s request context to allow future requests to access that information. For the session ID, we create an empty SessionHeader object, populate it using information from the LoginRequest, and store it in an instance variable for use by subsequent requests.

LoginResult loginResult;
sessionHeader = new SessionHeader();

try {
	loginResult = binding.login(getSforceUsername(), getSforcePassword(), null);
	Map<string, object=""> requestContext = ((BindingProvider)binding).getRequestContext();
	requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, loginResult.getServerUrl());
  
	sessionHeader.setSessionId(loginResult.getSessionId());
} catch (InvalidIdFault e) {
	logger.error("Invalid ID [" + getSforceUsername() + 
               "] while attempting to log in to web service.", e);
	throw new CommunicationException(e);
} catch (LoginFault e) {
	logger.error("Login error occurred while attempting to log in to web service.", e);
	throw new InvalidCredentialsException(e);
} catch (UnexpectedErrorFault e) {
	logger.error("Unexpected error occurred while attempting to log in to web service.", e);
	throw new CommunicationException(e);
}

Note that the binding.login() method does throw a set of checked exceptions which must be handled appropriately. An InvalidIdFault exception is thrown if the ID passed in to the login() method is not a valid ID field. A LoginFault exception is thrown if the password/token field is not valid for the user specified. An UnexpectedErrorFault is the “catch-all” exception thrown if a deviant condition occurs on the server when it tries to process the login request; if the SOAP message is truncated, for example.

Also, note that there are two method calls that I did not flesh out for this example; getSforceUsername() and getSforcePassword(). Both are simple getters for private instance variables; setters for these attributes exist as well. The sforceUsername attribute contains the username used to authenticate against the Salesforce.com web services endpoint; the sforcePassword attribute contains the concatenated value of the Salesforce.com password and security token for this user.

Performing Queries Against the Web Services Endpoint

Once the login process has succeeded, we are free to use the binding object instance to perform queries against the Salesforce.com API. I will not go into details on how the Salesforce.com query language works, but if you are familiar with SQL, it should seem pretty familiar. For this example, I’ll simply discuss the method we use to retrieve a single Salesforce.com proxy object.

The method that we created for retrieving a single object instance by ID is structured as follows:

@SuppressWarnings("unchecked")
public Type findById(String id) {
	List ids = createIDListForID(id);	
	List qResult = null;
	Type result = null;
	
	login();
		
	try {
		qResult = binding.retrieve(createFieldString(getAllFields()), getSObjectType(), 
                        ids, sessionHeader, null, null, null); 
	} catch (InvalidSObjectFault e) {
		logger.error("Invalid Sobject encountered while querying the web service.", e);
		throw new CommunicationException(e);
	} catch (InvalidFieldFault e) {
		logger.error("Invalid Field Fault encountered while querying the web service.", e);
		throw new CommunicationException(e);
	} catch (InvalidIdFault e) {
		logger.error("Invalid Id Fault encountered while querying the web service.", e);
		throw new CommunicationException(e);
	} catch (MalformedQueryFault e) {
		logger.error("Web service endpoint indicates that the query is malformed" +
                      " while querying the web service.", e);
		throw new CommunicationException(e);
	} catch (UnexpectedErrorFault e) {
		logger.error("Generic Unexpected Fault encountered while querying the web service.", e);
		throw new CommunicationException(e);
	} finally {
		logout();
	}

	if (qResult != null && qResult.size() == 1) {
		result = (Type) qResult.get(0);
	} else {
		logger.debug("Query resulted in [" + ((qResult == null)?null:qResult.size()) + 
                      "] records, rather than only one.");
	}
	
	return result;
}

The vast majority of this method is pretty self-explanatory; the important part to highlight is the binding.retrieve() call. This method takes in the following arguments:

  1. a String value containing a comma-delimited list of the fields in the target object to populate
  2. a String object containing the name of the object type to query and return
  3. a List of IDs for objects to return
  4. a SessionHeader instance for the SOAP request
  5. a QueryOptions instance
  6. a MruHeader instance
  7. a PackageVersionHeader instance

For the purposes of this example, we are ignoring the last three; essentially, we only want to perform a simple query against the Salesforce.com web service.

The bulk of the work is performed by one line of code:

qResult = binding.retrieve(createFieldString(getAllFields()), getSObjectType(), 
      ids, sessionHeader, null, null, null); 

This code builds the list of fields for the SObject to be retrieved (by way of an intermediary method to be implemented by the concrete Manager class), retrieves the SObject type to be passed in (once again, by way of an intermediary method), passes in the list of IDs to be retrieved, and passes in the SessionHeader instance that we populated in the login() method. Pretty straightforward.

Once again, there are a number of checked exceptions thrown by the retrieve() method, most of which should be self-explanatory.

Logging Out

Once you’re finished sending requests to the web services endpoint, it is necessary to log out to invalidate the SOAP session. The process for this is quite simple:

protected void logout() {
	try {
		binding.logout(sessionHeader);
	} catch (UnexpectedErrorFault e) {
		logger.error("Generic Unexpected Fault encountered while closing the web service.", e);
		throw new CommunicationException(e);
	}
}

Essentially, you need to call the logout() method on the Soap binding object instance, and as with the login method, catch any unexpected exceptions that are thrown.

That wraps up this introduction on how to access the Salesforce.com API in Java.

Share This