1

I am trying to integrate google books api to salesforce. FOr this i am trying Google Books™ Custom Adapter for Salesforce Connect

But it is giving me the following error - Am i missing any steps

  1. Type is not visible: DataSource.Connection
  2. @Override specified for non-overriding method: List MyThunder.BooksDataSourceConnection.sync().
  3. @Override specified for non-overriding method: DataSource.TableResult MyThunder.BooksDataSourceConnection.query(DataSource.QueryContext)
  4. @Override specified for non-overriding method: List MyThunder.BooksDataSourceConnection.search(DataSource.SearchContext)

Apex class -

/**
 *   Extends the DataSource.Connection class to enable
 *   Salesforce to sync the external system metadata
 *   schema and to handle queries and searches of the external
 *   data.
 **/
global class BooksDataSourceConnection extends
    DataSource.Connection {

    private DataSource.ConnectionParams connectionInfo;

    // Constructor for BooksDataSourceConnection.
    global BooksDataSourceConnection(DataSource.ConnectionParams
                                    connectionInfo) {
        this.connectionInfo = connectionInfo;
    }

    /**
     *   Called when an external object needs to get a list of 
     *   schema from the external data source, for example 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>();
        List<DataSource.Column> columns;
        columns = new List<DataSource.Column>();
        columns.add(getColumn('title'));
        columns.add(getColumn('description'));
        columns.add(getColumn('publishedDate'));
        columns.add(getColumn('publisher'));
        columns.add(DataSource.Column.url('DisplayUrl'));
        columns.add(DataSource.Column.text('ExternalId', 255));
        tables.add(DataSource.Table.get('googleBooks', 'title',
                                        columns));
        return tables;
    }

    /**
     *   Google Books API v1 doesn't support sorting,
     *   so we create a column with sortable = false.
     **/
    private DataSource.Column getColumn(String columnName) {
        DataSource.Column column = DataSource.Column.text(columnName,
                                                        255);
        column.sortable = false;
        return column;
    }

    /**
     *   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 contexts) {
        DataSource.Filter filter = contexts.tableSelection.filter;
        String url;
        if (contexts.tableSelection.columnsSelected.size() == 1 &&
        contexts.tableSelection.columnsSelected.get(0).aggregation ==
            DataSource.QueryAggregation.COUNT) {
            return getCount(contexts);
        }

        if (filter != null) {
            String thisColumnName = filter.columnName;
            if (thisColumnName != null &&
                thisColumnName.equals('ExternalId')) {
                url = 'https://www.googleapis.com/books/v1/' +
                    'volumes?q=' + filter.columnValue +
                    '&maxResults=1&id=' + filter.columnValue;
                return DataSource.TableResult.get(true, null,
                            contexts.tableSelection.tableSelected,
                            getData(url));
            }
            else {
                url = 'https://www.googleapis.com/books/' +
                    'v1/volumes?q=' + filter.columnValue +
                    '&id=' + filter.columnValue +
                    '&maxResults=40' + '&startIndex=';
            }
        } else {
            url = 'https://www.googleapis.com/books/v1/' +
                'volumes?q=america&' + '&maxResults=40' +
                '&startIndex=';
        }
        /**
         *   Google Books API v1 supports maxResults of 40
         *   so we handle pagination explicitly in the else statement
         *   when we handle more than 40 records per query.
         **/
        if (contexts.maxResults < 40) {
            return DataSource.TableResult.get(true, null,
                    contexts.tableSelection.tableSelected,
                    getData(url + contexts.offset));
        }
        else {
            return fetchData(contexts, url);
        }
     }

    /**
     *   Helper method to fetch results when maxResults is 
     *   greater than 40 (the max value for maxResults supported 
     *   by Google Books API v1).
     **/
    private DataSource.TableResult fetchData(
        DataSource.QueryContext contexts, String url) {
        Integer fetchSlot = (contexts.maxResults / 40) + 1;
        List<Map<String, Object>> data =
            new List<Map<String, Object>>();
        Integer startIndex = contexts.offset;
        for(Integer count = 0; count < fetchSlot; count++) {
            data.addAll(getData(url + startIndex));
            if(count == 0)
                contexts.offset = 41;
            else
                contexts.offset += 40;
        }

        return DataSource.TableResult.get(true, null,
                        contexts.tableSelection.tableSelected, data);
    }

    /**
     *   Helper method to execute count() query.
     **/
    private DataSource.TableResult getCount(
        DataSource.QueryContext contexts) {
        String url = 'https://www.googleapis.com/books/v1/' +
                    'volumes?q=america&projection=full';
        List<Map<String,Object>> response =
            DataSource.QueryUtils.filter(contexts, getData(url));
        List<Map<String, Object>> countResponse =
            new List<Map<String, Object>>();
        Map<String, Object> countRow =
            new Map<String, Object>();
        countRow.put(
            contexts.tableSelection.columnsSelected.get(0).columnName,
            response.size());
        countResponse.add(countRow);
        return DataSource.TableResult.get(contexts, countResponse);
    }

    /**
     *   Called to do a full text search and get results from
     *   the external system for SOSL queries and Salesforce
     *   global searches.
     *
     *   The SearchContext argument represents the query to run
     *   against a table in the external system.
     *
     *   Returns results for each table that the SearchContext
     *   requested to be searched.
     **/
    override global List<DataSource.TableResult> search(
        DataSource.SearchContext contexts) {
        List<DataSource.TableResult> results =
            new List<DataSource.TableResult>();

        for (Integer i =0; i< contexts.tableSelections.size();i++) {
            String entity = contexts.tableSelections[i].tableSelected;
            String url = 'https://www.googleapis.com/books/v1' +
                        '/volumes?q=' + contexts.searchPhrase;
            results.add(DataSource.TableResult.get(true, null,
                                                entity,
                                                getData(url)));
        }

        return results;
    }

    /**
     *   Helper method to parse the data.
     *   Returns a list of rows from the external system.
     **/
    public List<Map<String, Object>> getData(String url) {
        HttpResponse response = getResponse(url);
        String body = response.getBody();

        List<Map<String, Object>> rows =
            new List<Map<String, Object>>();

        Map<String, Object> responseBodyMap =
            (Map<String, Object>)JSON.deserializeUntyped(body);

    /**
     *   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 messages = (String)errors.get('message');
            throw new DataSource.OAuthTokenExpiredException(messages);
        }

        List<Object> sItems = (List<Object>)responseBodyMap.get('items');
        if (sItems != null) {
            for (Integer i=0; i< sItems.size(); i++) {
                Map<String, Object> item =
                    (Map<String, Object>)sItems[i];
                rows.add(createRow(item));
            }
        } else {
            rows.add(createRow(responseBodyMap));
        }

        return rows;
    }

    /**
     *   Helper method to populate a row based on source data.
     *
     *   The item argument 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 == 'id') {
                row.put('ExternalId', item.get(key));
            } else if (key == 'volumeInfo') {
                Map<String, Object> volumeInfoMap =
                    (Map<String, Object>)item.get(key);
                row.put('title', volumeInfoMap.get('title'));
                row.put('description',
                        volumeInfoMap.get('description'));
                row.put('DisplayUrl',
                        volumeInfoMap.get('infoLink'));
                row.put('publishedDate',
                        volumeInfoMap.get('publishedDate'));
                row.put('publisher',
                        volumeInfoMap.get('publisher'));
            }
        }
        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 HttpResponse getResponse(String url) {
        Http httpProtocol = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndPoint(url);
        request.setMethod('GET');
        request.setHeader('Authorization', 'Bearer '+
                        this.connectionInfo.oauthToken);
        HttpResponse response = httpProtocol.send(request);
        return response;
    }
}
  • Did you include the DataSource class? – Adrian Larson Feb 17 '18 at 15:59
  • @AdrianLarson DataSource is a standard library namespace. – sfdcfox Feb 17 '18 at 16:01
0

The last three errors are dependent upon the first. The first error states that DataSource.Connection is not visible. This likely means that you have not paid for the feature and are trying to use this in a production org. Your code should save just fine in a Developer Org. Edit: I just copy-pasted this in to my org, and it compiled just fine.

  • Hi @sfdcfox.. I tried compiling the class in my developer org..But still it is giving the same issue.. Do i need to set up anything in external data source ? – learnjqueery Feb 17 '18 at 17:53

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.