This example illustrates how to support external lookup relationships and multiple tables. An external lookup relationship links a child standard, custom, or external object to a parent external object. Each table can become an external object in the Salesforce org.
For this example to work, create a custom field on the Contact standard object. Name the custom field “github_username” and select the External ID and Unique attributes.
Class for StackOverflowDataSourceConnection
/** * Defines the connection to Stack Exchange API v2.2 to support * querying of Stack Overflow users (stackoverflowUser) * and posts (stackoverflowPost). * Extends the DataSource.Connection class to enable * Salesforce to sync the external system’s schema * and to handle queries of the external data. **/ global class StackOverflowDataSourceConnection extends DataSource.Connection { private DataSource.ConnectionParams connectionInfo; /** * Constructor for StackOverflowDataSourceConnection **/ global StackOverflowDataSourceConnection( DataSource.ConnectionParams connectionInfo) { this.connectionInfo = connectionInfo; } /** * Defines the schema for the external system. * 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>(); // Defines columns for the table of Stack OverFlow posts List<DataSource.Column> postColumns = new List<DataSource.Column>(); // Defines the external lookup field. postColumns.add(DataSource.Column.externalLookup( 'owner_id', 'stackoverflowUser__x')); postColumns.add(DataSource.Column.text('title', 255)); postColumns.add(DataSource.Column.text('view_count', 255)); postColumns.add(DataSource.Column.text('question_id',255)); postColumns.add(DataSource.Column.text('creation_date',255)); postColumns.add(DataSource.Column.text('score',255)); postColumns.add(DataSource.Column.url('link')); postColumns.add(DataSource.Column.url('DisplayUrl')); postColumns.add(DataSource.Column.text('ExternalId',255)); tables.add(DataSource.Table.get('stackoverflowPost','title', postColumns)); // Defines columns for the table of Stack OverFlow users List<DataSource.Column> userColumns = new List<DataSource.Column>(); userColumns.add(DataSource.Column.text('user_id', 255)); userColumns.add(DataSource.Column.text('display_name', 255)); userColumns.add(DataSource.Column.text('location',255)); userColumns.add(DataSource.Column.text('creation_date',255)); userColumns.add(DataSource.Column.url('website_url',255)); userColumns.add(DataSource.Column.text('reputation',255)); userColumns.add(DataSource.Column.url('link')); userColumns.add(DataSource.Column.url('DisplayUrl')); userColumns.add(DataSource.Column.text('ExternalId',255)); tables.add(DataSource.Table.get('stackoverflowUser', 'Display_name', userColumns)); 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 context) { DataSource.Filter filter = context.tableSelection.filter; String url; // Sets the URL to query Stack Overflow posts if (context.tableSelection.tableSelected .equals('stackoverflowPost')) { if (filter != null) { String thisColumnName = filter.columnName; if (thisColumnName != null && thisColumnName.equals('ExternalId')) url = 'https://api.stackexchange.com/2.2/' + 'questions/' + filter.columnValue + '?order=desc&sort=activity' + '&site=stackoverflow'; else url = 'https://api.stackexchange.com/2.2/' + 'questions' + '?order=desc&sort=activity' + '&site=stackoverflow'; } else { url = 'https://api.stackexchange.com/2.2/' + 'questions' + '?order=desc&sort=activity' + '&site=stackoverflow'; } // Sets the URL to query Stack Overflow users } else if (context.tableSelection.tableSelected .equals('stackoverflowUser')) { if (filter != null) { String thisColumnName = filter.columnName; if (thisColumnName != null && thisColumnName.equals('ExternalId')) url = 'https://api.stackexchange.com/2.2/' + 'users/' + filter.columnValue + '?order=desc&sort=reputation' + '&site=stackoverflow'; else url = 'https://api.stackexchange.com/2.2/' + 'users' + '?order=desc&sort=reputation&site=stackoverflow'; } else { url = 'https://api.stackexchange.com/2.2/' + 'users' + '?order=desc&sort=reputation' + '&site=stackoverflow'; } } /** * Filters, sorts, and applies limit and offset clauses. **/ List<Map<String, Object>> rows = DataSource.QueryUtils.process(context, getData(url)); return DataSource.TableResult.get(true, null, context.tableSelection.tableSelected, rows); } /** * 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) { String response = getResponse(url); List<Map<String, Object>> rows = new List<Map<String, Object>>(); Map<String, Object> responseBodyMap = (Map<String, Object>) JSON.deserializeUntyped(response); /** * Checks errors. **/ Map<String, Object> error = (Map<String, Object>)responseBodyMap.get('error'); if (error!=null) { List<Object> errorsList = (List<Object>)error.get('errors'); Map<String, Object> errors = (Map<String, Object>)errorsList[0]; String errorMessage = (String)errors.get('message'); throw new DataSource.OAuthTokenExpiredException(errorMessage); } List<Object> fileItems= (List<Object>)responseBodyMap.get('items'); if (fileItems != null) { for (Integer i=0; i < fileItems.size(); i++) { Map<String, Object> item = (Map<String, Object>)fileItems[i]; rows.add(createRow(item)); } } else { rows.add(createRow(responseBodyMap)); } 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 Map<String, Object> item parameter 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.equals('question_id') || key.equals('user_id')) { row.put('ExternalId', item.get(key)); } else if (key.equals('link')) { row.put('DisplayUrl', item.get(key)); } else if (key.equals('owner')) { Map<String, Object> ownerMap = (Map<String, Object>)item.get(key); row.put('owner_id', ownerMap.get('user_id')); } 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 String getResponse(String url) { // Perform callouts for production (non-test) results. Http httpProtocol = new Http(); HttpRequest request = new HttpRequest(); request.setEndPoint(url); request.setMethod('GET'); HttpResponse response = httpProtocol.send(request); return response.getBody(); } }
Class for providing StackOverflow post data sources.
/** * Extends the DataSource.Provider base class to create a * custom adapter for Salesforce Connect. The class informs * Salesforce of the functional and authentication * capabilities that are supported by or required to connect * to an external system. **/ global class StackOverflowPostDataSourceProvider extends DataSource.Provider { /** * For simplicity, this example declares that the external * system doesn’t require authentication by returning * AuthenticationCapability.ANONYMOUS as the sole entry * in the list of authentication capabilities. **/ override global List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() { List<DataSource.AuthenticationCapability> capabilities = new List<DataSource.AuthenticationCapability>(); capabilities.add( DataSource.AuthenticationCapability.ANONYMOUS); return capabilities; } /** * Declares the functional capabilities that the * external system supports, in this case * only SOQL queries. **/ override global List<DataSource.Capability> getCapabilities() { List<DataSource.Capability> capabilities = new List<DataSource.Capability>(); capabilities.add(DataSource.Capability.ROW_QUERY); return capabilities; } /** * Declares the associated DataSource.Connection class. **/ override global DataSource.Connection getConnection( DataSource.ConnectionParams connectionParams) { return new StackOverflowDataSourceConnection(connectionParams); } }