Salesforce Connect のループバックカスタムアダプタ
次の例は、クエリで絞り込みを処理する方法を示しています���簡潔に示すため、この例では、Salesforce 組織を、それ自体を外部システムとして同組織に接続しています。
LoopbackDataSourceConnection クラス
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 LoopbackDataSourceConnection
7 extends DataSource.Connection {
8
9 /**
10 * Constructors.
11 **/
12 global LoopbackDataSourceConnection(
13 DataSource.ConnectionParams connectionParams) {
14 }
15 global LoopbackDataSourceConnection() {}
16
17 /**
18 * Called when an external object needs to get a list of
19 * schema from the external data source, for example when
20 * the administrator clicks “Validate and Sync†in the
21 * user interface for the external data source.
22 **/
23 override global List<DataSource.Table> sync() {
24 List<DataSource.Table> tables =
25 new List<DataSource.Table>();
26 List<DataSource.Column> columns;
27 columns = new List<DataSource.Column>();
28 columns.add(DataSource.Column.text('ExternalId', 255));
29 columns.add(DataSource.Column.url('DisplayUrl'));
30 columns.add(DataSource.Column.text('Name', 255));
31 columns.add(
32 DataSource.Column.number('NumberOfEmployees', 18, 0));
33 tables.add(
34 DataSource.Table.get('Looper', 'Name', columns));
35 return tables;
36 }
37
38 /**
39 * Called to query and get results from the external
40 * system for SOQL queries, list views, and detail pages
41 * for an external object that’s associated with the
42 * external data source.
43 *
44 * The QueryContext argument represents the query to run
45 * against a table in the external system.
46 *
47 * Returns a list of rows as the query results.
48 **/
49 override global DataSource.TableResult
50 query(DataSource.QueryContext context) {
51 if (context.tableSelection.columnsSelected.size() == 1 &&
52 context.tableSelection.columnsSelected.get(0).aggregation ==
53 DataSource.QueryAggregation.COUNT) {
54 integer count = execCount(getCountQuery(context));
55 List<Map<String, Object>> countResponse =
56 new List<Map<String, Object>>();
57 Map<String, Object> countRow =
58 new Map<String, Object>();
59 countRow.put(
60 context.tableSelection.columnsSelected.get(0).columnName,
61 count);
62 countResponse.add(countRow);
63 return DataSource.TableResult.get(context,countResponse);
64 } else {
65 List<Map<String,Object>> rows = execQuery(
66 getSoqlQuery(context));
67 return DataSource.TableResult.get(context,rows);
68 }
69 }
70
71 /**
72 * Called to do a full text search and get results from
73 * the external system for SOSL queries and Salesforce
74 * global searches.
75 *
76 * The SearchContext argument represents the query to run
77 * against a table in the external system.
78 *
79 * Returns results for each table that the SearchContext
80 * requested to be searched.
81 **/
82 override global List<DataSource.TableResult>
83 search(DataSource.SearchContext context) {
84 return DataSource.SearchUtils.searchByName(context, this);
85 }
86
87 /**
88 * Helper method to execute the SOQL query and
89 * return the results.
90 **/
91 private List<Map<String,Object>>
92 execQuery(String soqlQuery) {
93 List<Account> objs = Database.query(soqlQuery);
94 List<Map<String,Object>> rows =
95 new List<Map<String,Object>>();
96 for (Account obj : objs) {
97 Map<String,Object> row = new Map<String,Object>();
98 row.put('Name', obj.Name);
99 row.put('NumberOfEmployees', obj.NumberOfEmployees);
100 row.put('ExternalId', obj.Id);
101 row.put('DisplayUrl',
102 URL.getSalesforceBaseUrl().toExternalForm() +
103 obj.Id);
104 rows.add(row);
105 }
106 return rows;
107 }
108
109 /**
110 * Helper method to get aggregate count.
111 **/
112 private integer execCount(String soqlQuery) {
113 integer count = Database.countQuery(soqlQuery);
114 return count;
115 }
116
117 /**
118 * Helper method to create default aggregate query.
119 **/
120 private String getCountQuery(DataSource.QueryContext context) {
121 String baseQuery = 'SELECT COUNT() FROM Account';
122 String filter = getSoqlFilter('',
123 context.tableSelection.filter);
124 if (filter.length() > 0)
125 return baseQuery + ' WHERE ' + filter;
126 return baseQuery;
127 }
128
129 /**
130 * Helper method to create default query.
131 **/
132 private String getSoqlQuery(DataSource.QueryContext context) {
133 String baseQuery =
134 'SELECT Id,Name,NumberOfEmployees FROM Account';
135 String filter = getSoqlFilter('',
136 context.tableSelection.filter);
137 if (filter.length() > 0)
138 return baseQuery + ' WHERE ' + filter;
139 return baseQuery;
140 }
141
142 /**
143 * Helper method to handle query filter.
144 **/
145 private String getSoqlFilter(String query,
146 DataSource.Filter filter) {
147 if (filter == null) {
148 return query;
149 }
150 String append;
151 DataSource.FilterType type = filter.type;
152 List<Map<String,Object>> retainedRows =
153 new List<Map<String,Object>>();
154 if (type == DataSource.FilterType.NOT_) {
155 DataSource.Filter subfilter = filter.subfilters.get(0);
156 append = getSoqlFilter('NOT', subfilter);
157 } else if (type == DataSource.FilterType.AND_) {
158 append =
159 getSoqlFilterCompound('AND', filter.subfilters);
160 } else if (type == DataSource.FilterType.OR_) {
161 append =
162 getSoqlFilterCompound('OR', filter.subfilters);
163 } else {
164 append = getSoqlFilterExpression(filter);
165 }
166 return query + ' ' + append;
167 }
168
169 /**
170 * Helper method to handle query subfilters.
171 **/
172 private String getSoqlFilterCompound(String operator,
173 List<DataSource.Filter> subfilters) {
174 String expression = ' (';
175 boolean first = true;
176 for (DataSource.Filter subfilter : subfilters) {
177 if (first)
178 first = false;
179 else
180 expression += ' ' + operator + ' ';
181 expression += getSoqlFilter('', subfilter);
182 }
183 expression += ') ';
184 return expression;
185 }
186
187 /**
188 * Helper method to handle query filter expressions.
189 **/
190 private String getSoqlFilterExpression(
191 DataSource.Filter filter) {
192 String columnName = filter.columnName;
193 String operator;
194 Object expectedValue = filter.columnValue;
195 if (filter.type == DataSource.FilterType.EQUALS) {
196 operator = '=';
197 } else if (filter.type ==
198 DataSource.FilterType.NOT_EQUALS) {
199 operator = '<>';
200 } else if (filter.type ==
201 DataSource.FilterType.LESS_THAN) {
202 operator = '<';
203 } else if (filter.type ==
204 DataSource.FilterType.GREATER_THAN) {
205 operator = '>';
206 } else if (filter.type ==
207 DataSource.FilterType.LESS_THAN_OR_EQUAL_TO) {
208 operator = '<=';
209 } else if (filter.type ==
210 DataSource.FilterType.GREATER_THAN_OR_EQUAL_TO) {
211 operator = '>=';
212 } else if (filter.type ==
213 DataSource.FilterType.STARTS_WITH) {
214 return mapColumnName(columnName) +
215 ' LIKE \'' + String.valueOf(expectedValue) + '%\'';
216 } else if (filter.type ==
217 DataSource.FilterType.ENDS_WITH) {
218 return mapColumnName(columnName) +
219 ' LIKE \'%' + String.valueOf(expectedValue) + '\'';
220 } else if (filter.type ==
221 DataSource.FilterType.LIKE_) {
222 return mapColumnName(columnName) +
223 ' LIKE \'' + String.valueOf(expectedValue) + '\'';
224 } else {
225 throwException(
226 'Implementing other filter types is left as an exercise for the reader: '
227 + filter.type);
228 }
229 return mapColumnName(columnName) +
230 ' ' + operator + ' ' + wrapValue(expectedValue);
231 }
232
233 /**
234 * Helper method to map column names.
235 **/
236 private String mapColumnName(String apexName) {
237 if (apexName.equalsIgnoreCase('ExternalId'))
238 return 'Id';
239 if (apexName.equalsIgnoreCase('DisplayUrl'))
240 return 'Id';
241 return apexName;
242 }
243
244 /**
245 * Helper method to wrap expression Strings with quotes.
246 **/
247 private String wrapValue(Object foundValue) {
248 if (foundValue instanceof String)
249 return '\'' + String.valueOf(foundValue) + '\'';
250 return String.valueOf(foundValue);
251 }
252}LoopbackDataSourceProvider クラス
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 LoopbackDataSourceProvider
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.ANONYMOUS);
21 capabilities.add(
22 DataSource.AuthenticationCapability.BASIC);
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
43 getConnection(DataSource.ConnectionParams connectionParams) {
44 return new LoopbackDataSourceConnection();
45 }
46}