Skip to content

Commit 8cb4b17

Browse files
sblaurockrubikzube
andauthored
[CH9786] Implement recommendation methods (#38)
* Add method to retrieve recommendations + request definition + tests. * Add in support for list of item ID's. * Add tests for recommendations responses. * Add ResultPod definition + test clarity. * CR fixes. * Minor tweak to comments Co-authored-by: Zubin Tiku <rubikzube@gmail.com>
1 parent 55998ed commit 8cb4b17

File tree

8 files changed

+531
-15
lines changed

8 files changed

+531
-15
lines changed

constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import io.constructor.client.models.AutocompleteResponse;
1919
import io.constructor.client.models.SearchResponse;
20+
import io.constructor.client.models.RecommendationsResponse;
2021
import io.constructor.client.models.ServerError;
2122
import okhttp3.HttpUrl;
2223
import okhttp3.MediaType;
@@ -97,7 +98,7 @@ public ConstructorIO(String apiToken, String apiKey, boolean isHTTPS, String hos
9798

9899
/**
99100
* Creates a constructor.io Client.
100-
*
101+
*
101102
* @param apiToken API Token, gotten from your <a href="https://constructor.io/dashboard">Constructor.io Dashboard</a>, and kept secret.
102103
* @param apiKey API Key, used publically in your in-site javascript client.
103104
* @param isHTTPS true to use HTTPS, false to use HTTP. It is highly recommended that you use HTTPS.
@@ -349,7 +350,7 @@ public boolean modifyItem(ConstructorItem item, String autocompleteSection, Stri
349350
/**
350351
* Queries the autocomplete service.
351352
*
352-
* Note that if you're making an autocomplete service on a website, you should definitely use our javascript client instead of doing it server-side!
353+
* Note that if you're making an autocomplete request for a website, you should definitely use our javascript client instead of doing it server-side!
353354
* That's important. That will be a solid latency difference.
354355
*
355356
* @param req the autocomplete request
@@ -369,7 +370,7 @@ public AutocompleteResponse autocomplete(AutocompleteRequest req, UserInfo userI
369370
/**
370371
* Queries the autocomplete service.
371372
*
372-
* Note that if you're making an autocomplete service on a website, you should definitely use our javascript client instead of doing it server-side!
373+
* Note that if you're making an autocomplete request for a website, you should definitely use our javascript client instead of doing it server-side!
373374
* That's important. That will be a solid latency difference.
374375
*
375376
* @param req the autocomplete request
@@ -405,7 +406,7 @@ public String autocompleteAsJSON(AutocompleteRequest req, UserInfo userInfo) thr
405406
/**
406407
* Queries the search service.
407408
*
408-
* Note that if you're making an search service on a website, you should definitely use our javascript client instead of doing it server-side!
409+
* Note that if you're making a search request for a website, you should definitely use our javascript client instead of doing it server-side!
409410
* That's important. That will be a solid latency difference.
410411
*
411412
* @param req the search request
@@ -425,7 +426,7 @@ public SearchResponse search(SearchRequest req, UserInfo userInfo) throws Constr
425426
/**
426427
* Queries the search service.
427428
*
428-
* Note that if you're making an search service on a website, you should definitely use our javascript client instead of doing it server-side!
429+
* Note that if you're making a search request for a website, you should definitely use our javascript client instead of doing it server-side!
429430
* That's important. That will be a solid latency difference.
430431
*
431432
* @param req the search request
@@ -479,7 +480,7 @@ public String searchAsJSON(SearchRequest req, UserInfo userInfo) throws Construc
479480
/**
480481
* Queries the search service with natural language processing.
481482
*
482-
* Note that if you're making a search service on a website, you should definitely use our javascript client instead of doing it server-side!
483+
* Note that if you're making a search request for a website, you should definitely use our javascript client instead of doing it server-side!
483484
* That's important. That will be a solid latency difference.
484485
*
485486
* @param req the natural language search request
@@ -499,7 +500,7 @@ public SearchResponse naturalLanguageSearch(NaturalLanguageSearchRequest req, Us
499500
/**
500501
* Queries the search service with natural language processing.
501502
*
502-
* Note that if you're making a search service on a website, you should definitely use our javascript client instead of doing it server-side!
503+
* Note that if you're making a search request for a website, you should definitely use our javascript client instead of doing it server-side!
503504
* That's important. That will be a solid latency difference.
504505
*
505506
* @param req the natural language search request
@@ -530,6 +531,66 @@ public String naturalLanguageSearchAsJSON(NaturalLanguageSearchRequest req, User
530531
}
531532
}
532533

534+
/**
535+
* Queries the recommendations service to retrieve results.
536+
*
537+
* Note that if you're making a recommendations request for a website, you should definitely use our javascript client instead of doing it server-side!
538+
* That's important. That will be a solid latency difference.
539+
*
540+
* @param req the recommendations request
541+
* @param userInfo optional information about the user
542+
* @return a recommendations response
543+
* @throws ConstructorException if the request is invalid.
544+
*/
545+
public RecommendationsResponse recommendations(RecommendationsRequest req, UserInfo userInfo) throws ConstructorException {
546+
try {
547+
String json = recommendationsAsJSON(req, userInfo);
548+
return createRecommendationsResponse(json);
549+
} catch (Exception exception) {
550+
throw new ConstructorException(exception);
551+
}
552+
}
553+
554+
/**
555+
* Queries the recommendations service to retrieve results.
556+
*
557+
* Note that if you're making an recommendations request for a website, you should definitely use our javascript client instead of doing it server-side!
558+
* That's important. That will be a solid latency difference.
559+
*
560+
* @param req the recommendations request
561+
* @param userInfo optional information about the user
562+
* @return a string of JSON
563+
* @throws ConstructorException if the request is invalid.
564+
*/
565+
public String recommendationsAsJSON(RecommendationsRequest req, UserInfo userInfo) throws ConstructorException {
566+
try {
567+
String path = "recommendations/v1/pods/" + req.getPodId();
568+
HttpUrl url = (userInfo == null) ? this.makeUrl(path) : this.makeUrl(path, userInfo);
569+
url = url.newBuilder()
570+
.addQueryParameter("num_results", String.valueOf(req.getNumResults()))
571+
.addQueryParameter("section", req.getSection())
572+
.build();
573+
574+
if (req.getItemIds() != null) {
575+
for (String itemId : req.getItemIds()) {
576+
url = url.newBuilder()
577+
.addQueryParameter("item_id", itemId)
578+
.build();
579+
}
580+
}
581+
582+
Request request = this.makeUserRequestBuilder(userInfo)
583+
.url(url)
584+
.get()
585+
.build();
586+
587+
Response response = clientWithRetry.newCall(request).execute();
588+
return getResponseBody(response);
589+
} catch (Exception exception) {
590+
throw new ConstructorException(exception);
591+
}
592+
}
593+
533594
/**
534595
* Makes a URL to issue the requests to. Note that the URL will automagically have the apiKey embedded.
535596
*
@@ -544,7 +605,7 @@ protected HttpUrl makeUrl(String path) throws UnsupportedEncodingException {
544605
.addQueryParameter("c", this.version)
545606
.host(this.host)
546607
.build();
547-
608+
548609
return url;
549610
}
550611

@@ -568,7 +629,7 @@ protected HttpUrl makeUrl(String path, UserInfo info) throws UnsupportedEncoding
568629
if (info.getUserId() != null) {
569630
url = url.newBuilder()
570631
.addQueryParameter("ui", String.valueOf(info.getUserId()))
571-
.build();
632+
.build();
572633
}
573634

574635
if (info.getUserSegments() != null) {
@@ -584,7 +645,7 @@ protected HttpUrl makeUrl(String path, UserInfo info) throws UnsupportedEncoding
584645

585646
/**
586647
* Creates a builder for an authorized request
587-
*
648+
*
588649
* @return Request Builder
589650
*/
590651
protected Builder makeAuthorizedRequestBuilder() {
@@ -595,7 +656,7 @@ protected Builder makeAuthorizedRequestBuilder() {
595656

596657
/**
597658
* Creates a builder for an end user request
598-
*
659+
*
599660
* @param info user information if available
600661
* @return Request Builder
601662
*/
@@ -612,7 +673,7 @@ protected Builder makeUserRequestBuilder(UserInfo info) {
612673

613674
/**
614675
* Checks the response from an endpoint and throws an exception if an error occurred
615-
*
676+
*
616677
* @return whether the request was successful
617678
*/
618679
protected static String getResponseBody(Response response) throws ConstructorException {
@@ -681,7 +742,21 @@ protected static SearchResponse createSearchResponse(String string) {
681742
}
682743

683744
/**
684-
* Moves metadata out of the result data for an array of results
745+
* Transforms a JSON string to a new JSON string for easy Gson parsing into an recommendations response.
746+
* Using JSON objects to acheive this is considerably less error prone than attempting to do it in
747+
* a Gson Type Adapter.
748+
*/
749+
protected static RecommendationsResponse createRecommendationsResponse(String string) {
750+
JSONObject json = new JSONObject(string);
751+
JSONObject response = json.getJSONObject("response");
752+
JSONArray results = response.getJSONArray("results");
753+
moveMetadataOutOfResultData(results);
754+
String transformed = json.toString();
755+
return new Gson().fromJson(transformed, RecommendationsResponse.class);
756+
}
757+
758+
/**
759+
* Moves metadata out of the result data for an array of results
685760
* @param results A JSON array of results
686761
*/
687762
protected static void moveMetadataOutOfResultData(JSONArray results) {
@@ -690,7 +765,7 @@ protected static void moveMetadataOutOfResultData(JSONArray results) {
690765
JSONObject result = results.getJSONObject(i);
691766
JSONObject resultData = result.getJSONObject("data");
692767
JSONObject metadata = new JSONObject();
693-
768+
694769
// Move unspecified properties in result data object to metadata object
695770
for (Object propertyKey : resultData.keySet()) {
696771
String propertyName = (String)propertyKey;
@@ -709,4 +784,4 @@ protected static void moveMetadataOutOfResultData(JSONArray results) {
709784
resultData.put("metadata", metadata);
710785
}
711786
}
712-
}
787+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.constructor.client;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Constructor.io Recommendations Request
7+
*/
8+
public class RecommendationsRequest {
9+
10+
private String podId;
11+
private int numResults;
12+
private List<String> itemIds;
13+
private String section;
14+
15+
/**
16+
* Creates a recommendations request
17+
*
18+
* @param podId the pod id to retrieve results from
19+
*/
20+
public RecommendationsRequest(String podId) throws IllegalArgumentException {
21+
if (podId == null) {
22+
throw new IllegalArgumentException("podId is a required parameter of type string");
23+
}
24+
25+
this.podId = podId;
26+
this.numResults = 10;
27+
this.itemIds = null;
28+
this.section = "Products";
29+
}
30+
31+
/**
32+
* @param podId the pod id to set
33+
*/
34+
public void setPodId(String podId) {
35+
this.podId = podId;
36+
}
37+
38+
/**
39+
* @return the pod id
40+
*/
41+
public String getPodId() {
42+
return podId;
43+
}
44+
45+
/**
46+
* @param numResults the num results to set
47+
*/
48+
public void setNumResults(int numResults) {
49+
this.numResults = numResults;
50+
}
51+
52+
/**
53+
* @return the num results
54+
*/
55+
public int getNumResults() {
56+
return numResults;
57+
}
58+
59+
/**
60+
* @param itemIds the item id's to set
61+
*/
62+
public void setItemIds(List<String> itemIds) {
63+
this.itemIds = itemIds;
64+
}
65+
66+
/**
67+
* @return the item id's
68+
*/
69+
public List<String> getItemIds() {
70+
return itemIds;
71+
}
72+
73+
/**
74+
* @param section the section to set
75+
*/
76+
public void setSection(String section) {
77+
this.section = section;
78+
}
79+
80+
/**
81+
* @return the section
82+
*/
83+
public String getSection() {
84+
return section;
85+
}
86+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.constructor.client.models;
2+
3+
import com.google.gson.annotations.SerializedName;
4+
5+
/**
6+
* Constructor.io Recommendations Response ... uses Gson/Reflection to load data in
7+
*/
8+
public class RecommendationsResponse {
9+
10+
@SerializedName("result_id")
11+
private String resultId;
12+
13+
@SerializedName("response")
14+
private RecommendationsResponseInner response;
15+
16+
/**
17+
* @return the resultId
18+
*/
19+
public String getResultId() {
20+
return resultId;
21+
}
22+
23+
/**
24+
* @return the response
25+
*/
26+
public RecommendationsResponseInner getResponse() {
27+
return response;
28+
}
29+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.constructor.client.models;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
import com.google.gson.annotations.SerializedName;
7+
8+
/**
9+
* Constructor.io Recommendations Response Inner ... uses Gson/Reflection to load data in
10+
*/
11+
public class RecommendationsResponseInner {
12+
13+
@SerializedName("results")
14+
private List<Result> results;
15+
16+
@SerializedName("total_num_results")
17+
private Integer totalNumberOfResults;
18+
19+
@SerializedName("pod")
20+
private ResultPod pod;
21+
22+
/**
23+
* @return the results
24+
*/
25+
public List<Result> getResults() {
26+
return results;
27+
}
28+
29+
/**
30+
* @return the totalNumberOfResults
31+
*/
32+
public Integer getTotalNumberOfResults() {
33+
return totalNumberOfResults;
34+
}
35+
36+
/**
37+
* @return the pod
38+
*/
39+
public ResultPod getPod() {
40+
return pod;
41+
}
42+
}

0 commit comments

Comments
 (0)