Newer Version Available

This content describes an older version of this product. View Latest

Google Drive™ Custom Adapter for Lightning Connect

This example illustrates how to use callouts and OAuth to connect to an external system, which in this case is the Google Drive™ online storage service.

For this example to work reliably, request offline access when setting up OAuth so that Salesforce can obtain and maintain a refresh token for your connections.

DriveDataSourceConnection Class

1/**
2 *   Extends the DataSource.Connection class to enable 
3 *   Salesforce to sync the external system’s schema 
4 *   and to handle queries and searches of the external data. 
5 **/
6global class DriveDataSourceConnection extends
7    DataSource.Connection {
8    private DataSource.ConnectionParams connectionInfo;
9    
10    /**
11     *   Constructor for DriveDataSourceConnection
12     **/
13    global DriveDataSourceConnection(
14        DataSource.ConnectionParams connectionInfo) {
15        this.connectionInfo = connectionInfo;
16    }
17    
18    /**
19     *   Called when an external object needs to get a list of 
20     *   schema from the external data source, for example when 
21     *   the administrator clicks “Validate and Sync” in the 
22     *   user interface for the external data source.   
23     **/
24    override global List<DataSource.Table> sync() {
25        List<DataSource.Table> tables =
26            new List<DataSource.Table>();
27        List<DataSource.Column> columns;
28        columns = new List<DataSource.Column>();
29        columns.add(DataSource.Column.text('title', 255));
30        columns.add(DataSource.Column.text('description',255));
31        columns.add(DataSource.Column.text('createdDate',255));
32        columns.add(DataSource.Column.text('modifiedDate',255));
33        columns.add(DataSource.Column.url('selfLink'));
34        columns.add(DataSource.Column.url('DisplayUrl'));
35        columns.add(DataSource.Column.text('ExternalId',255));
36        tables.add(DataSource.Table.get('googleDrive','title',
37            columns));
38        return tables;
39    }
40
41    /**
42     *   Called to query and get results from the external 
43     *   system for SOQL queries, list views, and detail pages 
44     *   for an external object that’s associated with the 
45     *   external data source.
46     *   
47     *   The QueryContext argument represents the query to run 
48     *   against a table in the external system.
49     *   
50     *   Returns a list of rows as the query results.
51     **/
52    override global DataSource.TableResult query(
53        DataSource.QueryContext context) {
54        DataSource.Filter filter = context.tableSelection.filter;
55        String url;
56        if (filter != null) {
57            String thisColumnName = filter.columnName;
58            if (thisColumnName != null && 
59                    thisColumnName.equals('ExternalId'))
60                url = 'https://www.googleapis.com/drive/v2/'
61                + 'files/' + filter.columnValue;
62            else
63                url = 'https://www.googleapis.com/drive/v2/'
64                + 'files';
65        } else {
66            url = 'https://www.googleapis.com/drive/v2/' 
67            + 'files';
68        }
69
70        /**
71         * Filters, sorts, and applies limit and offset clauses.
72         **/
73        List<Map<String, Object>> rows = 
74            DataSource.QueryUtils.process(context, getData(url));
75        return DataSource.TableResult.get(true, null,
76            context.tableSelection.tableSelected, rows);
77    }
78
79    /**
80     *   Called to do a full text search and get results from
81     *   the external system for SOSL queries and Salesforce
82     *   global searches.
83     *   
84     *   The SearchContext argument represents the query to run 
85     *   against a table in the external system.
86     *   
87     *   Returns results for each table that the SearchContext 
88     *   requested to be searched.
89     **/
90    override global List<DataSource.TableResult> search(
91        DataSource.SearchContext context) {
92        List<DataSource.TableResult> results =
93            new List<DataSource.TableResult>();
94
95        for (Integer i =0;i< context.tableSelections.size();i++) {
96            String entity = context.tableSelections[i].tableSelected;
97            String url = 
98                'https://www.googleapis.com/drive/v2/files'+
99                '?q=fullText+contains+\''+context.searchPhrase+'\'';
100            results.add(DataSource.TableResult.get(
101                true, null, entity, getData(url)));
102        }
103
104        return results;
105    }
106
107    /**
108     *   Helper method to parse the data.
109     *   The url argument is the URL of the external system.
110     *   Returns a list of rows from the external system.
111     **/
112    public List<Map<String, Object>> getData(String url) {
113        HttpResponse response = getResponse(url);
114
115        List<Map<String, Object>> rows =
116            new List<Map<String, Object>>();
117
118        Map<String, Object> responseBodyMap = (Map<String, Object>)
119            JSON.deserializeUntyped(response.getBody());
120
121        /**
122         *   Checks errors.
123         **/
124        Map<String, Object> error =
125            (Map<String, Object>)responseBodyMap.get('error');
126        if (error!=null) {
127            List<Object> errorsList =
128                (List<Object>)error.get('errors');
129            Map<String, Object> errors =
130                (Map<String, Object>)errorsList[0];
131            String errorMessage = (String)errors.get('message');
132            throw new DataSource.OAuthTokenExpiredException(errorMessage);
133        }
134
135        List<Object> fileItems=(List<Object>)responseBodyMap.get('items');
136        if (fileItems != null) {
137            for (Integer i=0; i < fileItems.size(); i++) {
138                Map<String, Object> item = 
139                    (Map<String, Object>)fileItems[i];
140                rows.add(createRow(item));  
141            }
142        } else {
143            rows.add(createRow(responseBodyMap));
144        }
145
146        return rows;
147    }
148
149    /**
150     *   Helper method to populate the External ID and Display 
151     *   URL fields on external object records based on the 'id' 
152     *   value that’s sent by the external system.
153     *   
154     *   The Map<String, Object> item parameter maps to the data 
155     *   that represents a row.
156     *   
157     *   Returns an updated map with the External ID and 
158     *   Display URL values.
159     **/
160    public Map<String, Object> createRow(
161        Map<String, Object> item){
162        Map<String, Object> row = new Map<String, Object>();
163        for ( String key : item.keySet() ) {
164            if (key == 'id') {
165                row.put('ExternalId', item.get(key));
166            } else if (key=='selfLink') {
167                row.put(key, item.get(key));
168                row.put('DisplayUrl', item.get(key));
169            } else {
170                row.put(key, item.get(key));
171            }
172        }
173        return row;
174    }
175    
176    /**
177     *   Helper method to make the HTTP GET call.
178     *   The url argument is the URL of the external system.
179     *   Returns the response from the external system.
180     **/
181    public HttpResponse getResponse(String url) {
182        Http httpProtocol = new Http();
183        HttpRequest request = new HttpRequest();
184        request.setEndPoint(url);
185        request.setMethod('GET');
186        request.setHeader('Authorization', 'Bearer '+
187            this.connectionInfo.oauthToken);
188        HttpResponse response = httpProtocol.send(request);
189        return response;
190    }
191}

DriveDataSourceProvider Class

1/**
2 *   Extends the DataSource.Provider base class to create a 
3 *   custom adapter for Lightning Connect. The class informs 
4 *   Salesforce of the functional and authentication 
5 *   capabilities that are supported by or required to connect 
6 *   to an external system.
7 **/
8global class DriveDataSourceProvider
9    extends DataSource.Provider {
10 
11    /**
12     *   Declares the types of authentication that can be used 
13     *   to access the external system
14     **/
15    override global List<DataSource.AuthenticationCapability>
16        getAuthenticationCapabilities() {
17        List<DataSource.AuthenticationCapability> capabilities =
18            new List<DataSource.AuthenticationCapability>();
19        capabilities.add(
20            DataSource.AuthenticationCapability.OAUTH);
21        capabilities.add(
22            DataSource.AuthenticationCapability.ANONYMOUS);
23        return capabilities;
24    }
25 
26    /**
27     *   Declares the functional capabilities that the 
28     *   external system supports.
29     **/
30    override global List<DataSource.Capability>
31        getCapabilities() {
32        List<DataSource.Capability> capabilities =
33            new List<DataSource.Capability>();
34        capabilities.add(DataSource.Capability.ROW_QUERY);
35        capabilities.add(DataSource.Capability.SEARCH);
36        return capabilities;
37    }
38
39    /**
40     *   Declares the associated DataSource.Connection class.
41     **/
42    override global DataSource.Connection getConnection(
43        DataSource.ConnectionParams connectionParams) {
44        return new DriveDataSourceConnection(connectionParams);
45    }
46}