A New Java Salesforce API Library

I’ve posted a lot of information about Salesforce over the years (Partner API Gotchas Part 1, Part 2, Part 3, Part 4, JAX-WS Tutorial Part 1, Part 2, Part 3, Part 4). I’m supplementing that with the release of an open source Java library for using the REST, Partner, Metadata and Apex APIs. You can get the source from the Palomino Labs open source project. The code is under active development but it is stable and ready for use. When I do cut a release, I’ll update this blog post to point to it.

Features

  • Easy-to-use wrappers around the APIs. The classes that tools generate for the SOAP APIs are clumsy and unintuitive (and not thread safe), so I’ve written better versions.
  • Limits concurrent API calls for Partner, Metadata and Apex connectors. This helps you avoid ‘concurrent request limit exceeded’ errors. (The REST API is harder to hit the limit with. I will add it to the request limiting system in the future.)
  • Transparent handling of INVALID_SESSION_ID for the Partner API. If someone else has closed the session ID you were using, the library will automatically re-login as needed.
  • Designed to be used with many different organizations simultaneously. If your app needs to talk to the orgs of many different customers all at the same time, it’s easy to do so. Of course, it’s also easy to work with just one org if that’s what you need.
  • Connections are reconfigurable at any time. You can update the login and password (for SOAP connections) or OAuth token (for REST connections) and the next API call you make will seamlessly use the updated information, even if it’s in another thread.
  • Designed with thread-safety in mind. Where practical, classes are thread-safe or immutable.
  • HTTP communication is gzip-compressed.
  • Integration with Metrics.
  • Well-tested, robust code.
  • Business-friendly Apache License (no GPL issues)

Getting started

First you’ll need to check out and build the code.

% git clone git://github.com/palominolabs/sf-api-connector.git
% cd sf-api-connector
% mvn clean install -DskipTests

Maven will probably need to download a bunch of plugins if you’re not a Maven user yet. Once it’s done, you’ll have installed the jars into your local maven repository. The reason tests are skipped is that some of the tests actually use Salesforce’s real API and are therefore restricted to only come from certain blocks of IP addresses, so those tests will fail unless you happen to be using my ISP. Also, the tests take quite some time to run. The SOAP-based APIs (Partner, Metadata and Apex) are in the soap-api-connector module and the REST API is in the rest-api-connector module. The dependencies to use for your pom.xml are shown below. You only need to include the dependency for the API that you’ll be using (that is, you don’t need both unless you’re using both). The dependencies are both SNAPSHOT versions because that’s what’s declared in the project’s pom.xml right now.

<dependency>
  <groupId>com.palominolabs.salesforce</groupId>
  <artifactId>soap-api-connector</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>com.palominolabs.salesforce</groupId>
  <artifactId>rest-api-connector</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>

Using the REST API

Most people can probably do what they need with the REST API, so I’ll start with that since it’s simpler. RestConnectionPool is the starting point for using the REST API. The class has a generic parameter to allow you to use any class you want to identify organizations. You might use the Salesforce organization id (the id that starts with ’00D’) or perhaps the primary key for a table in your DB that has a row for each organization. Make sure you use an immutable class with a useful hashCode()/equals() implementation since these objects will be used as keys in a Map. In this example I’ll use Integer. You’ll need to provide a MetricRegistry. If you’re not already using Metrics, just having a static final MetricRegistry that you use globally is a reasonable place to start; check the Metrics getting started guide for more.

// in your app setup, create a pool that's shared for the whole app
RestConnectionPool<Integer> pool = new RestConnectionPoolImpl<Integer>(yourMetricRegistry);

Before you can use the pool to get a connection for a specific org, you need to inform the pool about that org.

pool.configureOrg(orgId, host, oauthToken);

The host is the HTTP endpoint to use. Different orgs may be on different Salesforce clusters, so this is a per-org setting. You can extract the host from the partner server URL (see ConnectionBundle.getBindingConfig() in the SOAP API module or the tip later on in this article) if you don’t already have it. The OAuth token is something you’ll get by following the steps outlined in Salesforce’s OAuth docs. You can also use a session Id from the SOAP API as the OAuth token. You can configure a pool with as many orgs as you want. The pool is shared between all of them for efficiency. You can also reconfigure the data for an org in a pool at any time. Now that the pool is configured, let’s actually use a connection.

RestConnection connection = pool.getRestConnection(orgId);

SObject contact = connection.retrieve("Contact", new Id("0035000000kmAZJ"),
    Arrays.asList("FirstName", "LastName", "Email"));

System.out.println("Got a " + contact.getType() +
    " object with id " + contact.getId());

for (Map.Entry<String,String> entry: contact.getAllFields()) {
    System.out.println("Field <" + entry.getKey() + "> has value <" +
        entry.getValue() + ">");
}

SObject newLead = RestSObjectImpl.getNew("Lead");
newLead.setField("LastName", "Smith");
newLead.setField("Company", "FooCorp");

SaveResult result = connection.create(newLead);

if (result.isSuccess()) {
    System.out.println("Created a lead with id " + result.getId());
} else {
    System.out.println("Creation failed with errors " + result.getErrors());
}

Check the RestConnection interface to see what else you can do with it.

Using the SOAP APIs

The SOAP APIs are a little more complicated because there are three different WSDLs that share some common configuration. First you’ll need to pick a “partner key” or “client id”. This is a fairly arbitrary identifier that will be used to identify what application made an individual API call. Choose something at least mildly human-readable. As with RestConnectionPool, you can choose any class you like to be your org identifier. Here I’ll use Integer again.

ConnectionPool<Integer> pool = new ConnectionPoolImpl<Integer>(yourPartnerKey, yourMetricRegistry);

pool.configureOrg(orgId, username, password, maxConcurrentApiCalls);

Username and password are the Salesforce username and password of the person you want to log in as. The last parameter sets the max number of concurrent calls you want to happen per org. Fortunately in a recent update (API version 19 or 20, I think) Salesforce greatly relaxed the limits on concurrent connection usage to only apply to calls that take longer than 20 seconds, so you can set this limit fairly high unless you’re going to be doing long-running calls. See Salesforce’s API Usage Metering docs for more details. You can now get a ConnectionBundle for an org. A ConnectionBundle represents the per-org configuration needed for all SOAP connections.

ConnectionBundle bundle = pool.getConnectionBundle(orgId);

PartnerConnection partnerConn = bundle.getPartnerConnection();
ApexConnection apexConn = bundle.getApexConnection();
MetadataConnection metadataConn = bundle.getMetadataConnection();

// partner connection
System.out.println("User id is " + partnerConn.getUserInfo().getUserId());
System.out.println("First contact's email is " +
    partnerConn.query("SELECT Email FROM Contact LIMIT 1")
        .getSObjects().get(0).getField("Email"));

// metadata connection
List<FileProperties> fileProps = metadataConn
    .listMetadata(Arrays.asList(new ListMetadataQuery("CustomField")));
for (FileProperties fp: fileProps) {
    System.out.println("Custom field " + fp.getFullName() + " has id " + fp.getId());
}

// apex connection
ExecuteAnonResult result =
    apexConn.executeAnonyous("System.debug('test debug statement');");

System.out.println("Compile succeeded: " + result.isCompiled());
System.out.println("Debug log output: " + result.getDebugLog());

Note that the Metadata API example shows how to get custom field Ids, something that is impossible via the Partner API. The ConnectionBundle interface also lets you get the current configuration data for an org. You can use this to get the hostname that you need for RestConnectionPool. Using OAuth to get the token is recommended if that’s an option, but if not you can also get the current session Id from the configuration data and use that as the OAuth token.

BindingConfig bindingConfig = soapPool.getConnectionBundle(orgId).getBindingConfig();
String host = new URL(bindingConfig.getPartnerServerUrl()).getHost();
String token = bindingConfig.getSessionId();
restPool.configureOrg(orgId, host, token);

The library doesn’t support every single possible call in all of the APIs, but it does support most of them. I only implemented the calls I had occasion to use, so the Partner, Metadata and REST APIs have fairly complete support while the Apex API only supports a fraction of the available calls. If there’s a call that you use that the library doesn’t support, let me know and I’ll see what I can do. Similarly, if the library’s API doesn’t seem intuitive or doesn’t fit well with what you’re doing, I’d like to hear about that as well.

Posted by Marshall Pierce

Marshall specializes in highly tuned and immensely scalable web and mobile applications. Experienced in front-end web and iOS development, he constantly pushes the boundaries of the latest browsers and mobile platforms. He splits his time with back-end development, where he is considered a domain expert in Java concurrency, distributed systems, systems design, and network security. Prior to co-founding Palomino Labs, Marshall was director of software development at Ness Computing where he led their initial launch. Before Ness, Marshall was a senior software developer at Genius.com, where he built the best-in-class integration with Salesforce.com.

About Palomino Labs

Palomino Labs unlocks the potential of software to change people and industries. Our team of experienced software developers, designers, and product strategists can help turn any idea into reality.

See the Palomino Labs website for more information, or send us an email and let's start talking about how we can work together.