Newer Version Available
Google Drive™ Custom Adapter for Salesforce 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. The example also
shows how to avoid failing tests from web service callouts by returning mock responses for
test methods.
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 String 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);
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 static String mockResponse = '{' +
177 ' "kind": "drive#file",' +
178 ' "id": "12345",' +
179 ' "selfLink": "files/12345",' +
180 ' "title": "Mock File",' +
181 ' "mimeType": "application/text",' +
182 ' "description": "Mock response that’s used during tests",' +
183 ' "createdDate": "2016-04-20",' +
184 ' "modifiedDate": "2016-04-20",' +
185 ' "version": 1' +
186 '}';
187
188 /**
189 * Helper method to make the HTTP GET call.
190 * The url argument is the URL of the external system.
191 * Returns the response from the external system.
192 **/
193 public String getResponse(String url) {
194 if (System.Test.isRunningTest()) {
195 // Avoid callouts during tests. Return mock data instead.
196 return mockResponse;
197 } else {
198 // Perform callouts for production (non-test) results.
199 Http httpProtocol = new Http();
200 HttpRequest request = new HttpRequest();
201 request.setEndPoint(url);
202 request.setMethod('GET');
203 request.setHeader('Authorization', 'Bearer '+
204 this.connectionInfo.oauthToken);
205 HttpResponse response = httpProtocol.send(request);
206 return response.getBody();
207 }
208 }
209}DriveDataSourceProvider Class
1/**
2 * Extends the DataSource.Provider base class to create a
3 * custom adapter for Salesforce 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}