Salesforce.com is a very popular SaaS CRM. An essential task of a CRM system is enabling other applications to integrate smoothly with it, and toward this end, Salesforce.com provides several complementary APIs for use by their customers as well as by their partners (like us). We make heavy use of Salesforce.com’s API as part of integrating our customer’s Salesforce.com organizations into our products, and though the API is in general well designed and carefully documented, there are still a few dark corners that we’ve come across. Over the next few weeks, we’ll be describing some of these issues in more detail, as well as how to work around them, if possible.
SOAP Ain’t Simple
SOAP is a “metaprotocol”: a tool for building your own protocol. You might have a SOAP service that has a method
multiply which takes two ints and returns their product. SOAP defines how the communications to and from this service are formatted (namely, as XML over HTTP). This service would be declared in a WSDL file. The WSDL contains XML descriptions of the types of data that each method accepts and returns, as well as any exceptions they may throw. Someone who wished to use your SOAP service could download your provided WSDL and use it to generate code in their preferred language that could interact with your service. The generated code typically abstracts away all (or most) of the underlying HTTP and XML machinery.
Salesforce.com and SOAP
Salesforce.com provides several SOAP APIs, two of which are closely related: Enterprise and Partner. (You can download WSDLs for all of their APIs by logging in to your Salesforce.com account and navigating to Setup → App Setup → Develop → API. If you don’t have a Salesforce.com account, you can create a Developer account for free: click the button for Free Developer Edition Environment on http://developer.force.com/.)
To get started programming with the API in Java, you can use the JAX-WS reference implementation. Once you have the JAX-WS tools decompressed, you can use the wsimport script to generate stub classes from the WSDL you’d like to use:
sh bin/wsimport.sh -p [package for stub classes] -B-XautoNameResolution -d [output directory] [path to wsdl]
If you’re on Windows,
wsimport.bat is also provided.
The Enterprise API is defined differently for every single Salesforce.com organization. If your organization has custom objects that mine does not, then the WSDL that you get when you download your organization’s Enterprise WSDL will be different than my Enterprise WSDL because yours would, of course, have your custom objects in it. Because it is so specifically constructed for one organization’s specific Salesforce.com instance, the Enterprise API is best suited for developing tools that only target one Salesforce.com organization.
The Partner API provides access to the same data that the Enterprise API does, but in a more generic way. All Partner API WSDLs are the same: if you download the Partner API WSDL, you’ll get the same file I do. Rather than providing explicit types for every business object the way the Enterprise API does, the Partner API makes all object types accessible as “sObjects”. A Contact record would be represented as a sObject with a type field containing the string “Contact”. The Enterprise API, on the other hand, would contain WSDL types (and thus would yield generated classes) for Contact, Account, Lead, Opportunity, and every other type in your organization. For details, see the API documentation in Standard and Custom Object Basics → Core Data Types Used in API Calls.
Now that the basic terminology has been squared away, here’s a handful of issues we’ve run into with Salesforce.com’s Partner API. Since generated stub code will be used here and there, I’ll refer to those classes with a package of ‘sfstub’ to disambiguate them from other classes.
Inconsistent exceptions for bad password vs bad username
When you make the
login() call with a bad password, a sfstub.LoginFault_Exception (the generated class for the WSDL’s LoginFault element) gets thrown. This is the expected, documented behavior. When you make the call with a bad username, though, a generic javax.xml.ws.soap.SOAPFaultException is thrown instead. If you turn on http debugging (
com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump = true; for JAX-WS), you can see that the XML is indeed different. The fault for the bad password is transmitted as
<sf:LoginFault xsi:type="sf:LoginFault"> while the bad username’s fault is
<sf:fault xsi:type="sf:LoginFault" xmlns:sf="urn:fault.partner.soap.sforce.com">. The
sf:fault element in the WSDL is defined to have a type of
ApiFault as opposed to the
sf:LoginFault that is actually sent. I’m not a WSDL expert, though, so I’m not sure if the problem lies with Salesforce.com or with JAX-WS. Either way, it’s something to be aware of, especially since SOAPFaultException is an unchecked exception that you might not otherwise have reason to catch.
Update: This issue has now been fixed! The same exception gets thrown for both cases now.
Salesforce.com’s installation of Oracle is apparently configured to allow queries that are at most 64k in length (see this post on their developer forum). This has the consequence of imposing a difficult-to-predict limit on number of fields that can be fetched by a
retrieve(). If the fields you’re retrieving are simple fields like numbers, booleans, or dates, then you can retrieve hundreds of them in one call. (I have successfully fetched over five hundred simple fields in one call.) On the other hand, if the fields you’re retrieving are calculated fields, especially ones with complicated formulas, then the formulas are apparently handled by the database layer, as opposed to evaluating the formulas in the application layer. This means that the SQL query is much larger and may hit the 64k limit. There is no way to tell how much SQL is necessary for each field, so unfortunately all that can be done when this happens is to try splitting up the needed fields across multiple
The way Salesforce.com authenticates API calls is by examining a SOAP header that contains a session key. (The session key is set during the login process.) Session keys are allocated on a per-user basis, not on a per-login basis. This means that if you create two connections at the same time (e.g. calling
login() from two different threads), both will be using the same session key. Sessions can expire after a period of inactivity or by calling logout(). The inactivity timeout period is configurable in Setup → Administration Setup → Security Controls → Session Settings. (This timeout applies both to API sessions and to the sessions used by the web interface.) To illustrate the problem, let’s assume that there are two tools (T1 and T2) that you have set up to connect to your Salesforce.com organization. Naturally, you would configure them both to use your username and password. Let’s suppose that T2’s job takes longer than T1’s job. After running both tools for a while, T1 will complete its task. It is a commonly accepted programming best practice to release resources (mutexes, database connections, file handles, etc.) once they are no longer needed to prevent resource starvation, so it would be reasonable to assume that the author of T1 would see the
logout() call described in the API documentation and assume that it was a polite and proper thing to do to call logout() on the API connection once it was no longer needed. However, since T1 and T2 are both connecting as the same user, when T1 called
logout() it invalidated the session that was also in use by T2. This means that T2’s next API call will fail with an exception code of INVALID_SESSION_ID. This means that
logout() is a quite dangerous call: it will disrupt any other connections made using the same username. So, unless you have a specific reason to kill all active API connections for the user, including connections made by other tools, do not call
Continue with Part 2 of this series.