Lightning Connect came out in Winter ’15 and introduced a new way to integrate Salesforce with external data. It allows you to create a new kind of object that provides a live view of data residing in external systems. Until now, you were limited to integrating with OData web services, forcing you to build or buy OData producers hosted outside of Salesforce.
Summer ’15 is rolling out an exciting new feature that lets you create your own Lightning Connect adapters in Apex. Now, with the Apex Connector Framework, you can integrate with anything—well, anything that we can get to from Salesforce by using HTTP callouts. The Apex code runs within Salesforce, enabling you to develop seamless integration solutions entirely within the Force.com platform.
It’s quite simple to create your custom adapter for Lightning Connect. First, create a new Apex class that extends DataSource.Provider. This class is interrogated by the system to learn the set of capabilities that the external system support. It is an abstract class with the following methods:
Once you implement these methods, you can create an external data source. When you do so from Setup, you will see a new Type option that displays the name of your Apex Provider class, listed alongside the standard Lightning Connect adapters that Salesforce provides (an OData v2 and Salesforce adapter).
The initial release of the Apex Connector Framework offers a rather small list of capabilities.
- ROW_QUERY indicates that you can execute SOQL or view the external data in list views and detail pages.
- SEARCH indicates that this external data source should be used in Salesforce global searches and SOSL queries.
- REQUIRE_ENDPOINT indicates that a field to supply a URL should appear on the External Data Source setup UI.
Your Provider class needs to declare a set of authentication capabilities. These are the types of authentication you support: ANONYMOUS (no authentication), BASIC (for Basic authentication), OAUTH, and CERTIFICATE. The declared authentication capabilities determine what options appear in the Authentication Protocol field on the External Data Source page in Setup.
Lastly, you need to instantiate a Connection class, which we’ll define below. The getConnection method is called every time you execute a SOQL or SOSL query. You shouldn’t do anything expensive, such as callouts, while instantiating the Connection class.
The getConnection method receives connection parameters as an argument (these are supplied by the system). The connection parameters come from whatever the admin configures on the External Data Source page in Setup.
You can pass the connection parameters on to the Connection class. Note that anyone who can view debug logs can see the heap allocations for the object being instantiated and passed into the Connection class. For better security, I recommend that you use named credentials instead of supplying the credentials on the External Data Source page in Setup.
The following Provider class doesn’t pass along any credentials because authentication isn’t required in this example.
You will need to save the Connection class, defined below, before this class can be saved.
The Connection class is where the real work happens. (The Provider class just takes all the credit.) Your Connection class needs to extend DataSource.Connection, which is an abstract class that requires you to declare the following methods:
The sync method is called when you click the Validate and Sync button on the External Data Source page in Setup. The method returns the table schema as a DataSource.Table object that your Lightning Connect adapter can handle. An organization that uses your custom adapter might not create an external object for every single DataSource.Table. An organization might also have more than one external object backed by the same external DataSource.Table (though there aren’t many reasons to do so).
Each DataSource.Table declares a set of DataSource.Column objects that correspond to the columns on the external data store. If the DataSource.Table is synced, the columns become fields on the external object in Salesforce. Each column has a data type and a name.
The query method is called when users execute SOQL queries, and when the system executes SOQL queries as users browse external object list views and detail pages. The method receives an object that represents the query context, and that has the following properties:
The TableSelection object has properties that describe the table we are querying against, the columns we want to return, the criteria to determine what rows we should receive, and how we want the results ordered.
The Filter object is the WHERE clause from the query.
For a very simple comparative filter (such as “meaningOfLife = 42”), only the tableName, type (EQUALS), columnName (meaningOfLife), and columnValue (42) will be set. As a developer, you would be expected to ensure that you return only the rows that match this criteria.
We can also group filters. This will happen if the SOQL has AND, NOT, or OR statements. In this case, the type will be AND_, NOT_, or OR_, and the subfilters property will be set to a List of child Filters.
As a developer, it would be your job to acquire the data from wherever it comes from and provide the list of records that should be returned for the query. The records are returned as a List<Map<String,Object>>; the List is the collection of records, and each record is a Map where the key is one of the selected columns, and the Object is the value for that column on that record). The data can be sourced from a Salesforce organization, a CSV or other file, a callout response from a remote web service, or even generated entirely in Apex.
The search method is executed when you run a SOSL query or use the global search in Salesforce.
For simplicity, in the example below, we convert the search to a query that filters rows on the object’s name field. (External objects don’t have a standard Name field. Instead, you can designate any single text field as the name field for an external object.) The example isn’t complete, but we will explore search further in future blogs.
That’s a lot of information for now. Let’s see some code in action with a sample custom adapter that handles some basic queries. The example will source its data by executing SOQL against the Account standard object. While that isn’t very practical, it illustrates how to handle filters.
This sample will not work with SELECT COUNT() queries, which is a requirement for this to work in Salesforce1, nor will it support ORDER BY clauses. It doesn’t support queries that use the CONTAINS clause, so this object doesn’t work with Global Search.
We’ll enhance this example to cover those cases in subsequent blog posts.
And that’s it! Now you can create a data source, sync it to make an external object called Looper__x, make a custom tab for that object, browse list and detail views in Salesforce, and execute SOQL against it using the REST and SOAP APIs. Instead of taking the data from the Account object on the same org, you could modify your code to take the data from another org, or replace the SOQL queries with REST callouts to an external web service. The possibilities are endless.