jesperfj / force-rest-api

Java library for Force.com REST API
135 stars 114 forks source link

Salesforce REST API Connector

Lightweight library for building Salesforce apps with OAuth authentication and data access through the Salesforce REST API.

Usage

Releases are published on Maven Central. Include in your project with:

<dependency>
    <groupId>com.frejo</groupId>
    <artifactId>force-rest-api</artifactId>
    <version>0.0.45</version>
</dependency>

Build and link locally

$ git clone https://github.com/jesperfj/force-rest-api.git
$ cd force-rest-api
$ mvn install -DskipTests

The version number is never updated in SCM. So builds will always produce a module with version 0-SNAPSHOT. Add it as a dependency to your local builds with:

<dependency>
    <groupId>com.frejo</groupId>
    <artifactId>force-rest-api</artifactId>
    <version>0-SNAPSHOT</version>
</dependency>

To check out the source code for a particular version found in Maven Central, use the corresponding git tag, e.g:

 $ git clone https://github.com/jesperfj/force-rest-api.git
 $ cd force-rest-api
 $ git checkout force-rest-api-0.0.41

Authentication and Instantiation

API versions

Salesforce updates its API version with every Salesforce release (3 times per year). The new version is supposed to always be backwards compatible, so in theory it is safe to always use the latest API version. However force-rest-api is designed to be conservative. The API version used may change with new versions of the library, but for a given version of the library, the version will always be ApiVersion.DEFAULT_VERSION unless you explicitly set it to something different. You set the API version when you instantiate an ApiConfig:

ApiConfig mycfg = new ApiConfig().setApiVersionString("v99.0");

You can also use the ApiVersion enum to set the version:

ApiConfig mycfg = new ApiConfig().setApiVersion(ApiVersion.V38);

But the enum may not always have the version you need and there is no particular benefit to using it compared to using a simple String.

Username / Password Authentication

Authenticate using just login and password:

ForceApi api = new ForceApi(new ApiConfig()
    .setUsername("user@domain.com")
    .setPassword("password"));

OAuth Username/Password Authentication Flow

As documented here

ForceApi api = new ForceApi(new ApiConfig()
    .setUsername("user@domain.com")
    .setPassword("password")
    .setClientId("longclientidalphanumstring")
    .setClientSecret("notsolongnumeric"));

OAuth Web Server Flow

As documented here

String url = Auth.startOAuthWebServerFlow(new AuthorizationRequest()
    .apiConfig(new ApiConfig()
        .setClientId("longclientidalphanumstring")
        .setRedirectURI("https://myapp.mydomain.com/oauth"))
    .state("mystate"));

// redirect browser to url
// Browser will get redirected back to your app after user authentication at
// https://myapp.mydomain.com/oauth with a code parameter. Now do:

ApiSession s = Auth.completeOAuthWebServerFlow(new AuthorizationResponse()
    .apiConfig(new ApiConfig()
        .setClientId("longclientidalphanumstring")
        .setClientSecret("notsolongnumeric")
        .setRedirectURI("https://myapp.mydomain.com/oauth"))
    .code("alphanumericstringpassedbackinbrowserrequest"));

ForceApi api = new ForceApi(s.getApiConfig(),s);

Instantiate with existing accessToken and endpoint

If you already have an access token and endpoint (e.g. from a cookie), you can pass an ApiSession instance to ForceApi:

ApiConfig c = new ApiConfig()
    .setRefreshToken("refreshtoken")
    .setClientId("longclientidalphanumstring")
    .setClientSecret("notsolongnumeric"),

ApiSession s = new ApiSession()
    .setAccessToken("accessToken")
    .setApiEndpoint("apiEndpoint");

ForceApi api = new ForceApi(c,s);

CRUD and Query Operations

Get an SObject

Account res = api.getSObject("Account", "001D000000INjVe").as(Account.class);

This assumes you have an Account class defined with proper Jackson deserialization annotations. For example:

import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown=true)
public class Account {

    @JsonProperty(value="Id")
    String id;
    @JsonProperty(value="Name")
    String name;
    @JsonProperty(value="AnnualRevenue")
    private Double annualRevenue;
    @JsonProperty(value="externalId__c")
    String externalId;

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Double getAnnualRevenue() { return annualRevenue; }
    public void setAnnualRevenue(Double value) { annualRevenue = value; }
    public String getExternalId() { return externalId; }
    public void setExternalId(String externalId) { this.externalId = externalId; }
}

Create SObject

Account a = new Account();
a.setName("Test account");
String id = api.createSObject("account", a);

Update SObject

a.setName("Updated Test Account");
api.updateSObject("account", id, a);

Create or Update SObject

a = new Account();
a.setName("Perhaps existing account");
a.setAnnualRevenue(3141592.65);
api.createOrUpdateSObject("account", "externalId__c", "1234", a);

Delete an SObject

api.deleteSObject("account", id);

Query SObjects

QueryResult<Account> res = api.query("SELECT id FROM Account WHERE name LIKE 'Test account%'", Account.class);

CRUD operations on root path

Sometimes you want to do CRUD operations without the standard /services/data/<version> path prefix. To do this you can get a ForceApi instance that uses root path:

ForceApi api = new ForceApi(myConfig,mySession);
api.rootPath().get("/services/apexrest/myApexClass");

rootPath() returns a new ForceApi instance that uses root path for the get(), delete(), put(), post(), patch() and request() methods.

Working with API versions

You can inspect supported API versions and get more detailed info for each version using SupportedVersions:

SupportedVersions versions = api.getSupportedVersions();
System.out.println(versions.oldest());          // prints v20.0
System.out.println(versions.contains("v25.0")); // prints true

The set of supported versions may vary based on where your organization is located. New versions are introduced 3 times a year and are rolled out gradually. During the rollout period, some organizations will have the latest version while others will not. The oldest supported version for REST API is v20.0. Salesforce API versions go further back than v20.0, but REST API does not support those older versions.

There is a direct mapping between season/year and version numbers. You can translate between season/year and version number in this way:

ExtendedApiVersion v = new ExtendedApiversion(ExtendedApiVersion.Season.SPRING, 2012);
System.out.println(v.getVersionString());       // prints v21.0

ExtendedApiVersion is called "Extended" because it goes beyond what ApiVersion offers and can represent more details about an API version, e.g. its season, year and URL base.

Run Tests

This project has a mix of unit tests and integration tests that hit the actual API. To make the integration tests work you must set up a proper test fixture and reference it from environment variables. .testenv.sample contains a sample shell script indicating what variables must be set. Copy it to .testenv and once you have all the correct values, set it in the environment by running the shell command:

source .testenv

Login and password

You need credentials to a Force.com developer org to run tests against. These go in the username and password vaiables. Needless to say, don't use credentials for a production org containing sensitive data. If you don't have a developer org, sign up for one. It's free. Remember to append the security token to your chosen password in the password variable.

Client ID and Secret

Once you have signed up for an org, create a Connected App:

Add externalId__c to Account SObject

Use the Force.com Web UI to add a custom field called externalId__c and mark it as an external ID field:

Create a second user for IP restrictions test

To test IP restrictions failure handling you need additional test setup:

Run Tests

Before running the whole test suite, it is a good idea to run a single login test to check if the configuration is correct. If the username/password is not configured correctly, the test suite will trigger an account lock-out due to the many failed attempts. Run a single test such as testSoapLogin with:

mvn -Dtest=com.force.api.AuthTest#testSoapLogin test

Now run tests with

$ mvn test

You will see some log messages that look like errors or warnings. That's expected and does not indicate test failures. You can add debug logging with:

$ mvn test -Dorg.slf4j.simpleLogger.defaultLogLevel=debug

Interactive end-to-end OAuth handshake Test

This test is not run as part of the test suite because it requires manual intervention. Run it like this:

mvn -Dtest=com.force.api.EndToEndOAuthFlowExample test

Cutting a Release

This project now uses Alex Fontaine's release process because the release plugin is a pretty insane piece of software that should never exist. The pom.xml version number checked into SCM is always 0-SNAPSHOT. Mapping releases back to source code now relies on git tags only.

The project is set up to release to Maven Central. If you have forked it and want to deploy your own version, you will need to update groupId and set up your own Sonatype credentials and GPG. Assuming this is all correctly set up. Here's how you cut a new release:

First ensure all your code is checked in (with git status or the like). Then run tests one extra time and also test javadoc generation since it's easy to introduce errors in javadoc comments that will break the deploy:

$ mvn test javadoc:javadoc

Note. You must have JAVA_HOME set for this to succeed. On Mac, set it with

$ export JAVA_HOME=$(/usr/libexec/java_home)

Now find the latest version number with git tag (or in Maven central depending on what you trust most). Bump the version number to that plus one:

$ mvn versions:set -DnewVersion=<new-version>

For example:

$ mvn versions:set -DnewVersion=0.0.50

This will update pom.xml locally to the new version and leaving it uncommitted (which is what you want). Now run

$ mvn scm:tag

This tags the local and remote repository with the full module name, e.g. force-rest-api-0.0.50. Now deploy:

$ mvn clean deploy -DperformRelease

That command will fail if you don't have gpg installed. Install on MacOS with

$ brew install gpg

When you're done, reset the local version change to pom.xml with:

$ mvn versions:revert

Just as a validation, try to push local changes including tags:

$ git push origin master --tags

There should be nothing to push. If something is messed up, delete the tags in Github and in your local repo and start over.

Release History

0.0.46

0.0.45

0.0.44

0.0.43

0.0.42

0.0.41

0.0.40

0.0.39

0.0.38

0.0.37

0.0.36

0.0.35

0.0.34

0.0.33

0.0.32

0.0.31

0.0.30

0.0.29

0.0.28

0.0.23

0.0.22

0.0.21

0.0.20

0.0.19

0.0.18

0.0.17

0.0.16

0.0.15

0.0.14

0.0.13

0.0.12

0.0.11

0.0.10 was botched. Missed a checkin

0.0.10

0.0.9

0.0.8

0.0.7

0.0.6

Project Goals:

License

BSD 2-clause license

Author

Jesper Joergensen