4

I am creating a custom External Data Source extending the DataSource.Connection classes and the DataSource.Provider classes. The external object is located in a salesforce organization.

For the query, search, upsertRows, and deleteRows can I just use the Salesforce Rest API Calls?

http://docs.releasenotes.salesforce.com/en-us/summer15/release-notes/rn_forcecom_external_data_apex_adapter.htm

http://docs.releasenotes.salesforce.com/en-us/winter16/release-notes/rn_forcecom_external_data_writeable_objects.htm

2

You can certainly use REST API or any other callout in a custom External Data Source adapter.

You can see my custom adapter demo for an example. It's too long to cut and paste here in its entirety, but the gist is, you translate the DataSource.QueryContext to the type of REST call you want to make, execute the callout, and then map the results back to a DataSource.TableResult.

override global DataSource.TableResult query(DataSource.QueryContext c) {
    if (c.tableSelection.columnsSelected.size() == 1 && c.tableSelection.columnsSelected.get(0).aggregation == DataSource.QueryAggregation.COUNT) {
        integer count = execCount(getCountQuery(c));
        List < Map < String, Object >> countResponse = new List < Map < String, Object >> ();
        Map < String, Object > countRow = new Map < String, Object > ();
        countRow.put(
            c.tableSelection.columnsSelected.get(0).columnName,
            count);
        countResponse.add(countRow);
        return DataSource.TableResult.get(c,
            countResponse);
    } else if (c.queryMoreToken != null) {
        return execQueryMore(c);
    } else {
        return execQuery(c);
    }
}

How you translate the QueryContext to the REST call will depend on what you are doing (counting records, querying records, or paging over records from a query you already executed). In the case that you are doing a query, you would try to convert the QueryContext to a SOQL query.

private DataSource.TableResult execQuery(DataSource.QueryContext c) {
    string soqlQuery = getSoqlQuery(c);
    String jsonResponse = restClient.query(soqlQuery);
    return parseQueryResponse(c, jsonResponse);
}
private string getSoqlQuery(DataSource.QueryContext c) {
    string baseQuery = 'SELECT Id,Name,NumberOfEmployees,Description FROM ' + remoteSObject;
    string filter = getSoqlFilter('', c.tableSelection.filter);
    if (filter.length() > 0) return baseQuery + ' WHERE ' + filter;
    return baseQuery;
}

private string getSoqlFilter(string query, DataSource.Filter filter) {
    if (filter == null) {
        return query;
    }
    string append;
    DataSource.FilterType type = filter.type;
    List<Map<String,Object>> retainedRows = new List<Map<String,Object>> ();
    if (type == DataSource.FilterType.NOT_) {
        DataSource.Filter subfilter = filter.subfilters.get(0);
        append = getSoqlFilter('NOT', subfilter);
    } else if (type == DataSource.FilterType.AND_) {
        append = getSoqlFilterCompound('AND', filter.subfilters);
    } else if (type == DataSource.FilterType.OR_) {
        append = getSoqlFilterCompound('OR', filter.subfilters);
    } else {
        append = getSoqlFilterExpression(filter);
    }
    return query + ' ' + append;
}

private string getSoqlFilterCompound(string op, List < DataSource.Filter > subfilters) {
    string expression = ' (';
    boolean first = true;
    for (DataSource.Filter subfilter: subfilters) {
        if (first) first = false;
        else expression += ' ' + op + ' ';
        expression += getSoqlFilter('', subfilter);
    }
    expression += ') ';
    return expression;
}

private string getSoqlFilterExpression(DataSource.Filter filter) {
    string columnName = filter.columnName;
    string op;
    object expectedValue = filter.columnValue;
    if (filter.type == DataSource.FilterType.EQUALS) {
        op = '=';
    } else if (filter.type == DataSource.FilterType.NOT_EQUALS) {
        op = '<>';
    } else if (filter.type == DataSource.FilterType.LESS_THAN) {
        op = '<';
    } else if (filter.type == DataSource.FilterType.GREATER_THAN) {
        op = '>';
    } else if (filter.type == DataSource.FilterType.LESS_THAN_OR_EQUAL_TO) {
        op = '<=';
    } else if (filter.type == DataSource.FilterType.GREATER_THAN_OR_EQUAL_TO) {
        op = '>=';
    } else if (filter.type == DataSource.FilterType.STARTS_WITH) {
        return mapColumnName(columnName) + ' LIKE \'' + String.valueOf(expectedValue) + '%\'';
    } else if (filter.type == DataSource.FilterType.ENDS_WITH) {
        return mapColumnName(columnName) + ' LIKE \'%' + String.valueOf(expectedValue) + '\'';
    } else if (filter.type == DataSource.FilterType.CONTAINS) {
        return mapColumnName(columnName) + ' LIKE \'%' + String.valueOf(expectedValue) + '%\'';
    } else if (filter.type == DataSource.FilterType.LIKE_) {
        return mapColumnName(columnName) + ' LIKE \'' + String.valueOf(expectedValue) + '\'';
    } else {
        throwException('Implementing other filter types is left as an exercise for the reader: ' + filter.type);
    }
    return mapColumnName(columnName) + ' ' + op + ' ' + wrapValue(expectedValue);
}

And, finally, make the REST callout:

public String query(String soql) {
    String encodedSoql = EncodingUtil.urlEncode(soql, 'UTF-8');
    String url = connectionParams.endpoint + 'services/data/v35.0/query?q=' + encodedSoql;
    String jsonResponse = sendGet(url);
    return jsonResponse;
}

private String sendGet(String url) {
    HttpRequest request = request(url, 'GET');
    HttpResponse response = http.send(request);
    validateStatusCode(url, response);
    return response.getBody();
}

But, if all you are doing is returning data from objects (including external objects) on another org, the Salesforce Adapter would be a better solution because it does all of this work for you. Using a custom Apex adapter would make sense only if you intend to merge queries against multiple objects into one "view" or otherwise programatically alter how your external object represents data from its source.

| improve this answer | |
  • Awesome this is pretty much exactly what I need. Thanks! – TemporaryFix Dec 22 '15 at 17:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.