Salesforce Lightning Connect : Apex Adapter using REST API with Authentication

Use Case

This blog talks about how to consume External API like REST or SOAP through Salesforce Lightning connect

Problem Statment Detailed 

There are three ways to access the external data in salesforce through lightning connect

Access Data in Another Organization with the Salesforce Adapter for Lightning Connect
Access External Data with the OData 2.0 or 4.0 Adapter for Lightning Connect
Access External Data with a Custom Adapter for Lightning Connect

First two are configuration based solution and quite easy to implement. I would talk about the third topic is here . Though I am still open if you have questions for first two topics.

Apex Adapter  has to be used when Salesforce wants to access external database which is neither a salesforce  not  residing on  Odata.

So we can access any external data which is residing on any of other platform. So if you have some external API(notoData) can consume by lightning connect. So for e,g Consuming REST or SOAP API is very much possible by lightning connect.

As we know the beauty of Lightning connect is not keeping the data in Salesforce , consuming Rest and SOAP API will make lightning connect more powerful

All the examples of Apex adapter on internet either use Salesforce or Google API not the REST or SOAP with Authentication like oAuth 2.0

Salesforce provide very few options for the Authentication provider

\"Screen

So if you want to access Google API with Salesforce lightning connect , it can be done . Its mentioned in example above.

But if you want to access any Webservice which is hosted on Rest API , require to further down one step

Solution

So to access Rest API with the oAuth or some other authentication, it has to be done in the Code of the connection class.

Example

We want to access external API with last name = ‘Micheal’ , it hosted on REST API.

Working code
/**
 *   Extends the DataSource.Connection class to enable 
*   Salesforce to sync the external system’s metadata schema 
 *   and to handle queries and searches of the external data. 
 **/
global class DriveDataSourceConnection extends DataSource.Connection {
    private DataSource.ConnectionParams connectionInfo;
    
    /**
     *   Constructor for DriveDataSourceConnection.
    **/
    global DriveDataSourceConnection(DataSource.ConnectionParams connectionInfo) {
        this.connectionInfo = connectionInfo;
    }
    
    /**
     *   Called when the administrator clicks “Validate and Sync”
     *   in the user interface for the external data source.
     **/
    override global List<DataSource.Table> sync() {
        List<DataSource.Table> tables =new List<DataSource.Table>();
        List<DataSource.Column> columns;
        columns = new List<DataSource.Column>();
        columns.add(DataSource.Column.text(\’Last Name\’,255));
        columns.add(DataSource.Column.url(\’DisplayUrl\’));
        columns.add(DataSource.Column.text(\’ExternalId\’,255));
        tables.add(DataSource.Table.get(\’Consumer\’,\’title\’,columns));
        return tables;         
    }
    /**
     *   Called to query and get results from the external 
     *   system for SOQL queries, list views, and detail pages 
     *   for an external object that’s associated with the 
     *   external data source.
     *   The queryContext argument represents the query to run 
     *   against a table in the external system.
     *   Returns a list of rows as the query results.
     **/
    override global DataSource.TableResult query(DataSource.QueryContext c){
        String url;
        /**
         *   Filters, sorts, and applies limit and offset clauses.
         **/
        url = \’https://dummyURL/consumers/search\’;
        List<Map<String, Object>> rows;
        rows = DataSource.QueryUtils.process(c, getData(url));
        return DataSource.TableResult.get(true, null,c.tableSelection.tableSelected, rows);
    }
    /**
     *   Called to do a full text search and get results from
     *   the external system for SOSL queries and Salesforce
     *   global searches.
     *   The searchContext argument represents the query to run 
     *   against a table in the external system.
     *   Returns results for each table that the searchContext 
     *   requested to be searched.
     **/
    
    override global List<DataSource.TableResult> search(DataSource.SearchContext c){
        List<DataSource.TableResult> results = new List<DataSource.TableResult>();
        String url;
        url = \’https://dummyURL/consumers/search\’;
        results.add(DataSource.TableResult.get(true, null , entity, getData(url)));  
        return results;
    }
    
    /**
     *   Helper method to parse the data.
     *   The url argument is the URL of the external system.
     *   Returns a list of rows from the external system.
     **/
    public List<Map<String, Object>> getData(String url){
        HttpResponse response = getResponse(url);
        List<Map<String, Object>> rows = new List<Map<String, Object>>();
       List<Object> mData = (List<Object>)JSON.deserializeUntyped(response.getBody());
       for (Integer k=0; k< mData.size(); k++){        
            Map<String, Object> m = (Map<String, Object>)mData[k];         
            List<Object> fileItems=(List<Object>)m.get(\’records\’);
           rows.add(createRow(m));
       }
       return rows;
    }
    /**
     *   Helper method to populate the External ID and Display 
     *   URL fields on external object records based on the ‘id’ 
     *   value that’s sent by the external system.
     *   The item argument maps to the data that 
     *   represents a row.
     *   Returns an updated map with the External ID and 
     *   Display URL values.
     **/
    public Map<String, Object> createRow(Map<String, Object> item){
    Map<String, Object> row = new Map<String, Object>();
    for ( String key : item.keySet() ){
        if (key == \’idRef\’) {                            
            row.put(\’ExternalId\’, item.get(key));            
            
        } else if (key==\’lastName\’){  
            row.put(\’Last Name\’, item.get(key));
            
        } else {
            row.put(key, item.get(key));
        }
    }
    return row;
    }
    
    /**
     *   Helper method to make the HTTP GET call.
     *   The url argument is the URL of the external system.
     *   Returns the response from the external system.
     **/
    public HttpResponse getResponse(String url) {
        //Retrieve token
         HttpRequest reqToken = new HttpRequest();                                                                                                                     
        HttpResponse resToken = new HttpResponse();
        Http http = new Http();
        reqToken.setEndpoint(\’https://dummyURL/oauth/token\’);            
        reqToken.setMethod(\’POST\’);     creating the HTTP request
        reqToken.setHeader(\’Authorization\’, \’Basic X19lcXVpZmF4X19jbGllbnRfXzo=\’);     
        reqToken.setBody(\’grant_type=\’+EncodingUtil.urlEncode(\’password\’, \’UTF-8\’) +
        \’&scope=\’+EncodingUtil.urlEncode(\’read+write\’, \’UTF-8\’) +                                                       
        \’&username=\’+EncodingUtil.urlEncode(\’csa\’, \’UTF-8\’) +
        \’&password=\’+EncodingUtil.urlEncode(\’blah\’, \’UTF-8\’));       
        resToken = http.send(reqToken);       
   
        JSONParser parser = JSON.createParser(resToken.getBody());
        String accesstoken;
         String endPointUrl;
        parser.nextToken();
        while (parser.nextToken() != null) {
            if ((parser.getCurrentToken() == JSONToken.FIELD_NAME)){
                String fieldName = parser.getText();
                if(fieldName == \’access_token\’) {
                    parser.nextToken();
                    accesstoken = parser.getText();
                }
            }
        }  
        Http httpProtocol = new Http();        
        HttpRequest integrationRequest = new HttpRequest();
        integrationRequest.setEndpoint(url);
        integrationRequest.setMethod(\’POST\’);
        integrationRequest.setHeader(\’Authorization\’, \’Bearer \’ + accesstoken);
        integrationRequest.setHeader(\’Content-Type\’, \’application/json\’);       
        integrationRequest.setBody(\'{\”lastName\” : \”Person\”}\’);   
        try{ 
            HttpResponse response = httpProtocol.send(integrationRequest);
            return response;
        } Catch(Exception e){
            return null;
        }            
    }
}
Code Description

Sync Method :

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.

In this example we are creating an external object : Consumer , it has three fields in schema 1. External Id , DisplayURL and last Name . These column gets created automatically with a table Consumer

Query Method

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,

Search Method

The search method is executed when you run a SOSL query or use the global search in Salesforce.

getResponse

This method is actually consuming the REST api. We are doing User Password authentication here

  • Creating HTTP Request for Authentication

-Doing Authentication

  • Getting the HTTP Response

-Getting the Access Token for the actual HTTP request

  • Creating the HTTP Request with Last Name as ‘Michel’
  • Getting the Data and sending back to method
Configuration  Changes

So if you see we can access other REST API with authentication through lightning connect . To remove authentication from External Data source , please do the following config.

\"Screen

Select Identity Type as : Named Principal and Auth Protocol as No Authentication. If you select it as oAuth2.0 , would have to select a Auth Provider .

Summary 

Salesforce Lightning connect is a really seamless and powerful tool and can be customised to an extent level .You can play around with Query and Search method with the help of visual force pages.

5 Comments

  • Very nice, thank you this is some great information. Looking forward to more like this.

  • Anuja

    Nice work with the description of the problem. I especially liked the part where you explained the central ideas with figures and related the ideas with elements of code.
    Looking forward to reading more on the subject.

  • Purvi

    Perfect solution to the problem i am working on….thanks for having it blogged !

  • Renu

    Very structured information on lightning connect… Hope to read more blogs on same in future..

  • Anjali Gautam

    Thank you for sharing this .. Very useful information.

Leave a Reply

Your email address will not be published. Required fields are marked *

Are you also interested in writing a blog on a Salesforce topic?Write it on Salesforcerush and let’s spread the knowledge together. We don’t care if you are a beginner so don’t hesitate.

Edit Template

About

We at Salesforcerush translate Salesforce knowledge into blogs.The idea is to circulate practical salesforce experience across all kinds of audiences. Salesforcerush is a platform for all experts who strive to share their knowledge with others.It would have a variety of topics like certifications, new releases, product features, and industry-specific information.We need your support to grow this platform.

Categories

Contact us

Collaborate With Us