HTTP Endpoint Database

HttpEndpointDatabase allows you to integrate with a third-party service that uses an HTTP API. A subclass of HttpEndpointDatabase can convert Dari queries to HTTP API calls and return the results as if they were retrieved from the Dari underlying database. The search experience is the same for the end user, whether a query is on the underlying database or converted to an HTTP request to a third-party API.

HttpEndpointDatabase includes methods for extracting Dari query predicates and values for constructing HTTP API calls. It also includes methods that you can override to open and close connections to a third-party service, and to save objects to the underlying Dari database.

An HttpEndpointDatabase subclass must implement the doInitialize and readPartial methods inherited from the Dari AbstractDatabase and Database classes, respectively.

The following example shows a simple implementation of HttpEndpointDatabase that leverages the Getty REST Search API to retrieve images from that service. The GettyDatabase class gets the predicate and associated values from a Dari query, constructs an HTTP request that is sent to the Getty API, and returns the Getty response as a Dari PaginatedResult object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class GettyDatabase extends HttpEndpointDatabase {

   private static final Logger LOGGER = LoggerFactory.getLogger(GettyDatabase.class);
   public static final String API_KEY_SUB_SETTING = "apiKey";
   public static final String API_URL_SUB_SETTING = "apiUrl";
   public static final String DEFAULT_API_URL = "https://api.gettyimages.com/v3";

   private String apiUrl;
   private String apiKey;

   public String getApiUrl() {
      return apiUrl;
   }

   public void setApiUrl(String apiUrl) {
      this.apiUrl = apiUrl;
   }

   public String getApiKey() {
      return apiKey;
   }

   public void setApiKey(String apiKey) {
      this.apiKey = apiKey;
   }

   @Override // Must be implemented
   protected void doInitialize(String settingsKey, Map<String, Object> settings) {
      setApiUrl(ObjectUtils.to(String.class, settings.get(API_URL_SUB_SETTING)));
      setApiKey(ObjectUtils.to(String.class, settings.get(API_KEY_SUB_SETTING)));
   }

   @Override // Must be implemented
   public <T> PaginatedResult<T> readPartial(Query<T> query, long offset, int limit) {

      String apiKey = getApiKey();
      if (apiKey == null) {
         return PaginatedResult.empty();
      }

      try {
         String apiUrl = ObjectUtils.firstNonBlank(getApiUrl(), DEFAULT_API_URL);
         Predicate predicate = query.getPredicate();

         final String fields = "id,title,display_set";

         URL url = new URL(StringUtils.addQueryParameters(apiUrl + "/search/images",
                     "phrase", findQueryStringPredicateValue(predicate),
                     "fields", fields,
                     "page", offset + 1,
                     "page_size", limit));

         // Construct HTTP objects to send request for images to Getty service
         HttpClient httpClient = HttpClientBuilder.create().build();
         HttpGet get = new HttpGet(url.toString());
         get.addHeader("Api-Key", apiKey);
         HttpResponse response = httpClient.execute(get);

         if (response == null) return PaginatedResult.empty();

         Map<String, Object> responseMap = (Map<String, Object>) ObjectUtils.fromJson(EntityUtils.toString(response.getEntity()));
         List<Object> images = ((List<Object>) responseMap.get("images"));

         // Return PaginatedResult
         if (images != null) {
             List<Object> items = images.stream()
                     .map(image -> (Map<String, Object>) image)
                     .filter(Objects::nonNull)
                     .collect(Collectors.toList());

             return Optional.ofNullable((Long) responseMap.get("result_count"))
                     .map(count -> new PaginatedResult(offset, limit, count, items))
                     .orElse(new PaginatedResult(offset, limit, items));
         }
         else return PaginatedResult.empty();

      } catch (IOException error) {
         LOGGER.error("Unable to perform Getty request!", error);
      }
      return PaginatedResult.empty();
   }
}

In the previous snippet—

  • Lines 4–6 set final variables that refer to the URL and security key for the Getty REST API. The API_URL_SUB_SETTING specifies the host for the API, and the API_KEY_SUB_SETTING specifies the security key issued by the service. These values are set in a client UI such as Brightspot. A default host site is set on DEFAULT_API_URL in the event that the Getty API URL is not set in Brightspot.
  • Lines 27–31 contain the required implementation for the doInitialize method, which sets the Getty URL and security key. Note, however, that instead of calling doInitialize, a client can set the Getty URL and security key using the public setter methods setApiUrl and setApiKey.
  • Line 36 gets the Getty security key, and line 42 gets the URL for the Getty REST API.
  • Line 43 gets the predicate from the Dari query passed to the method.
  • Line 45 initializes a fields variable that specifies the data to be retrieved from the Getty service.
  • Lines 47–51 create a URL object to be passed in the HTTP request to the Getty service. The URL specifies the HTTP endpoint (“/search/images”) and the fields variable. Also included in the URL is phrase, the value of the Dari predicate. This value is retrieved with the HttpEndpointDatabase#findQueryStringPredicateValue method.
  • Lines 54–57 create HTTP-related objects and submit the request to the Getty service. JSON data returned from the service is set on response.
  • Lines 61–62 create a list of objects from the JSON image data.
  • Lines 65–74 create a list of objects that are used to construct a PaginatedResult object returned by the method.

SourceDatabaseProvider Implementation

Objects retrieved from a third-party service are represented as ExternalItem objects in Brightspot. External items are associated with a source database provider, which specifies the Database object that retrieves data from a third-party service.

For GettyDatabase that integrates with the Getty third-party service, the GettyDatabaseProvider is the source database provider. It provides the glue between the ExternalItem objects that represent retrieved Getty images and GettyDatabase. GettyDatabaseProvider implements SourceDatabaseProvider#get, which creates the GettyDatabase instance and sets the Getty API key on the instance. The key is retrieved from the Brightspot UI.

public class GettyDatabaseProvider implements SourceDatabaseProvider {

   @Override
   public Database get(ObjectType type) {
      GettyDatabase db = new GettyDatabase();
      db.setEnvironment(Database.Static.getDefault().getEnvironment());
      db.setApiKey(Application.Static.getInstance(CmsTool.class).as(GettySettings.class).getApiKey());
      return db;
   }
}