Skip to content
This repository was archived by the owner on Jan 31, 2022. It is now read-only.

Commit 0c25420

Browse files
author
Clément Le Provost
committed
Merge branch 'feat/createIfNotExists'
2 parents 980be72 + ca60347 commit 0c25420

File tree

3 files changed

+260
-15
lines changed

3 files changed

+260
-15
lines changed

algoliasearch/src/main/java/com/algolia/search/saas/Index.java

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ public Request saveObjectsAsync(final @NonNull JSONArray objects, @NonNull Compl
284284
/**
285285
* Partially update an object (asynchronously).
286286
*
287+
* **Note:** This method will create the object if it does not exist already. If you don't wish to, you can use
288+
* {@link #partialUpdateObjectAsync(JSONObject, String, boolean, CompletionHandler)} and specify `false` for the
289+
* `createIfNotExists` argument.
290+
*
287291
* @param partialObject New value/operations for the object.
288292
* @param objectID Identifier of object to be updated.
289293
* @param completionHandler The listener that will be notified of the request's outcome.
@@ -293,14 +297,36 @@ public Request partialUpdateObjectAsync(final @NonNull JSONObject partialObject,
293297
return getClient().new AsyncTaskRequest(completionHandler) {
294298
@NonNull
295299
@Override JSONObject run() throws AlgoliaException {
296-
return partialUpdateObject(partialObject, objectID);
300+
return partialUpdateObject(partialObject, objectID, null);
301+
}
302+
}.start();
303+
}
304+
305+
/**
306+
* Partially update an object (asynchronously).
307+
*
308+
* @param partialObject New value/operations for the object.
309+
* @param objectID Identifier of object to be updated.
310+
* @param createIfNotExists Whether the object should be created if it does not exist already.
311+
* @param completionHandler The listener that will be notified of the request's outcome.
312+
* @return A cancellable request.
313+
*/
314+
public Request partialUpdateObjectAsync(final @NonNull JSONObject partialObject, final @NonNull String objectID, final boolean createIfNotExists, CompletionHandler completionHandler) {
315+
return getClient().new AsyncTaskRequest(completionHandler) {
316+
@NonNull
317+
@Override JSONObject run() throws AlgoliaException {
318+
return partialUpdateObject(partialObject, objectID, createIfNotExists);
297319
}
298320
}.start();
299321
}
300322

301323
/**
302324
* Partially update several objects (asynchronously).
303325
*
326+
* **Note:** This method will create the objects if they do not exist already. If you don't wish to, you can use
327+
* {@link #partialUpdateObjectsAsync(JSONArray, boolean, CompletionHandler)} and specify `false` for the
328+
* `createIfNotExists` argument.
329+
*
304330
* @param partialObjects New values/operations for the objects. Each object must contain an <code>objectID</code>
305331
* attribute.
306332
* @param completionHandler The listener that will be notified of the request's outcome.
@@ -310,7 +336,25 @@ public Request partialUpdateObjectsAsync(final @NonNull JSONArray partialObjects
310336
return getClient().new AsyncTaskRequest(completionHandler) {
311337
@NonNull
312338
@Override JSONObject run() throws AlgoliaException {
313-
return partialUpdateObjects(partialObjects);
339+
return partialUpdateObjects(partialObjects, true);
340+
}
341+
}.start();
342+
}
343+
344+
/**
345+
* Partially update several objects (asynchronously).
346+
*
347+
* @param partialObjects New values/operations for the objects. Each object must contain an <code>objectID</code>
348+
* attribute.
349+
* @param createIfNotExists Whether objects should be created if they do not exist already.
350+
* @param completionHandler The listener that will be notified of the request's outcome.
351+
* @return A cancellable request.
352+
*/
353+
public Request partialUpdateObjectsAsync(final @NonNull JSONArray partialObjects, final boolean createIfNotExists, CompletionHandler completionHandler) {
354+
return getClient().new AsyncTaskRequest(completionHandler) {
355+
@NonNull
356+
@Override JSONObject run() throws AlgoliaException {
357+
return partialUpdateObjects(partialObjects, createIfNotExists);
314358
}
315359
}.start();
316360
}
@@ -733,9 +777,13 @@ private String encodeAttributes(List<String> attributesToRetrieve, boolean forUR
733777
* @param partialObject the object attributes to override
734778
* @throws AlgoliaException
735779
*/
736-
protected JSONObject partialUpdateObject(JSONObject partialObject, String objectID) throws AlgoliaException {
780+
protected JSONObject partialUpdateObject(JSONObject partialObject, String objectID, Boolean createIfNotExists) throws AlgoliaException {
737781
try {
738-
return client.postRequest("/1/indexes/" + encodedIndexName + "/" + URLEncoder.encode(objectID, "UTF-8") + "/partial", partialObject.toString(), false);
782+
String path = "/1/indexes/" + encodedIndexName + "/" + URLEncoder.encode(objectID, "UTF-8") + "/partial";
783+
if (createIfNotExists != null) {
784+
path += "?createIfNotExists=" + createIfNotExists.toString();
785+
}
786+
return client.postRequest(path, partialObject.toString(), false);
739787
} catch (UnsupportedEncodingException e) {
740788
throw new RuntimeException(e);
741789
}
@@ -747,16 +795,17 @@ protected JSONObject partialUpdateObject(JSONObject partialObject, String object
747795
* @param inputArray the array of objects to update (each object must contains an objectID attribute)
748796
* @throws AlgoliaException
749797
*/
750-
protected JSONObject partialUpdateObjects(JSONArray inputArray) throws AlgoliaException {
798+
protected JSONObject partialUpdateObjects(JSONArray inputArray, boolean createIfNotExists) throws AlgoliaException {
751799
try {
800+
final String action = createIfNotExists ? "partialUpdateObject" : "partialUpdateObjectNoCreate";
752801
JSONArray array = new JSONArray();
753802
for (int n = 0; n < inputArray.length(); n++) {
754803
JSONObject obj = inputArray.getJSONObject(n);
755-
JSONObject action = new JSONObject();
756-
action.put("action", "partialUpdateObject");
757-
action.put("objectID", obj.getString("objectID"));
758-
action.put("body", obj);
759-
array.put(action);
804+
JSONObject operation = new JSONObject();
805+
operation.put("action", action);
806+
operation.put("objectID", obj.getString("objectID"));
807+
operation.put("body", obj);
808+
array.put(operation);
760809
}
761810
return batch(array);
762811
} catch (JSONException e) {

algoliasearch/src/test/java/com/algolia/search/saas/AssertCompletionHandler.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import java.util.ArrayList;
66
import java.util.List;
77

8-
import static junit.framework.Assert.fail;
9-
108
/**
119
* This class helps coping with assertions in callbacks.
1210
* As AssertionErrors are thrown silently when an assertion fails in a callback,
@@ -17,12 +15,21 @@ abstract class AssertCompletionHandler implements CompletionHandler {
1715
private final CompletionHandler handler;
1816
private final List<AssertCompletionHandler> innerHandlers = new ArrayList<>();
1917

18+
/**
19+
* Global registry of all created completion handlers. It is cleared when the static `checkAllHandlers()` method is
20+
* called.
21+
*/
22+
private static List<AssertCompletionHandler> allHandlers = new ArrayList<>();
23+
2024
public AssertCompletionHandler() {
2125
this.handler = new CompletionHandler() {
2226
@Override public void requestCompleted(JSONObject content, AlgoliaException error) {
2327
doRequestCompleted(content, error);
2428
}
2529
};
30+
synchronized (AssertCompletionHandler.class) {
31+
allHandlers.add(this);
32+
}
2633
}
2734

2835
abstract public void doRequestCompleted(JSONObject content, AlgoliaException error);
@@ -39,13 +46,26 @@ public void addInnerHandler(AssertCompletionHandler handler) {
3946
*/
4047
public void checkAssertions() {
4148
if (error != null) {
42-
fail("At least one assertion failed: " + error);
49+
// Throwing the original exception maintains the stack trace... though I am not entirely sure why. =:)
50+
// (An alternative would be to chain the exception.)
51+
throw error;
4352
}
4453
for (AssertCompletionHandler h : innerHandlers) {
4554
h.checkAssertions();
4655
}
4756
}
4857

58+
/**
59+
* Check assertions on all handlers created since the last call to this method (or since the beginning of the
60+
* process if this method was never called).
61+
*/
62+
public synchronized static void checkAllHandlers() {
63+
for (AssertCompletionHandler handler : allHandlers) {
64+
handler.checkAssertions();
65+
}
66+
allHandlers.clear();
67+
}
68+
4969
@Override final public void requestCompleted(JSONObject content, AlgoliaException error) {
5070
try {
5171
handler.requestCompleted(content, error);

algoliasearch/src/test/java/com/algolia/search/saas/IndexTest.java

Lines changed: 178 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,14 @@ public void setUp() throws Exception {
9696
}
9797

9898
@Override
99-
public void tearDown() throws Exception {
100-
client.deleteIndex(indexName);
99+
public void tearDown() {
100+
try {
101+
client.deleteIndex(indexName);
102+
}
103+
catch (AlgoliaException e) {
104+
fail(e.getMessage());
105+
}
106+
AssertCompletionHandler.checkAllHandlers();
101107
}
102108

103109
@Test
@@ -911,4 +917,174 @@ public void testGetObjectsAttributes() throws AlgoliaException {
911917
fail(e.getMessage());
912918
}
913919
}
920+
921+
@Test
922+
public void testPartialUpdateObject() throws Exception {
923+
JSONObject partialObject = new JSONObject().put("city", "Paris");
924+
index.partialUpdateObjectAsync(partialObject, ids.get(0), new AssertCompletionHandler() {
925+
@Override
926+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
927+
assertNull(error);
928+
String taskID = content.optString("taskID");
929+
index.waitTaskAsync(taskID, new AssertCompletionHandler() {
930+
@Override
931+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
932+
assertNull(error);
933+
index.getObjectAsync(ids.get(0), new AssertCompletionHandler() {
934+
@Override
935+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
936+
assertNotNull(content);
937+
assertEquals(ids.get(0), content.optString("objectID"));
938+
}
939+
});
940+
}
941+
});
942+
}
943+
});
944+
}
945+
946+
@Test
947+
public void testPartialUpdateObjectNoCreate() throws Exception {
948+
final String objectID = "unknown";
949+
final JSONObject partialObject = new JSONObject().put("city", "Paris");
950+
951+
// Partial update on a nonexistent object with `createIfNotExists=false` should not create the object.
952+
index.partialUpdateObjectAsync(partialObject, objectID, false, new AssertCompletionHandler() {
953+
@Override
954+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
955+
assertNull(error);
956+
String taskID = content.optString("taskID");
957+
index.waitTaskAsync(taskID, new AssertCompletionHandler() {
958+
@Override
959+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
960+
assertNull(error);
961+
index.getObjectAsync(objectID, new AssertCompletionHandler() {
962+
@Override
963+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
964+
assertNotNull(error);
965+
assertEquals(404, error.getStatusCode());
966+
967+
// Partial update on a nonexistent object with `createIfNotExists=true` should create the object.
968+
index.partialUpdateObjectAsync(partialObject, objectID, true, new AssertCompletionHandler() {
969+
@Override
970+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
971+
assertNull(error);
972+
String taskID = content.optString("taskID");
973+
index.waitTaskAsync(taskID, new AssertCompletionHandler() {
974+
@Override
975+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
976+
assertNull(error);
977+
index.getObjectAsync(objectID, new AssertCompletionHandler() {
978+
@Override
979+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
980+
assertNotNull(content);
981+
assertEquals(objectID, content.optString("objectID"));
982+
}
983+
});
984+
}
985+
});
986+
}
987+
});
988+
}
989+
});
990+
}
991+
});
992+
}
993+
});
994+
}
995+
996+
@Test
997+
public void testPartialUpdateObjects() throws Exception {
998+
final JSONArray partialObjects = new JSONArray()
999+
.put(new JSONObject().put("objectID", ids.get(0)).put("city", "Paris"))
1000+
.put(new JSONObject().put("objectID", ids.get(1)).put("city", "Berlin"));
1001+
1002+
index.partialUpdateObjectsAsync(partialObjects, false, new AssertCompletionHandler() {
1003+
@Override
1004+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1005+
assertNull(error);
1006+
String taskID = content.optString("taskID");
1007+
index.waitTaskAsync(taskID, new AssertCompletionHandler() {
1008+
@Override
1009+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1010+
assertNull(error);
1011+
index.getObjectsAsync(ids, new AssertCompletionHandler() {
1012+
@Override
1013+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1014+
assertNotNull(content);
1015+
JSONArray results = content.optJSONArray("results");
1016+
assertNotNull(results);
1017+
assertEquals(2, results.length());
1018+
for (int i = 0; i < partialObjects.length(); ++i) {
1019+
assertEquals(ids.get(i), results.optJSONObject(i).optString("objectID"));
1020+
assertEquals(partialObjects.optJSONObject(i).optString("city"), results.optJSONObject(i).optString("city"));
1021+
}
1022+
}
1023+
});
1024+
}
1025+
});
1026+
}
1027+
});
1028+
}
1029+
1030+
@Test
1031+
public void testPartialUpdateObjectsNoCreate() throws Exception {
1032+
final List<String> newIds = Arrays.asList("unknown", "none");
1033+
final JSONArray partialObjects = new JSONArray()
1034+
.put(new JSONObject().put("objectID", newIds.get(0)).put("city", "Paris"))
1035+
.put(new JSONObject().put("objectID", newIds.get(1)).put("city", "Berlin"));
1036+
1037+
index.partialUpdateObjectsAsync(partialObjects, false, new AssertCompletionHandler() {
1038+
@Override
1039+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1040+
assertNull(error);
1041+
String taskID = content.optString("taskID");
1042+
index.waitTaskAsync(taskID, new AssertCompletionHandler() {
1043+
@Override
1044+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1045+
assertNull(error);
1046+
index.getObjectsAsync(newIds, new AssertCompletionHandler() {
1047+
@Override
1048+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1049+
// NOTE: A multiple get objects doesn't return an error for nonexistent objects,
1050+
// but simply returns `null` for the missing objects.
1051+
assertNotNull(content);
1052+
JSONArray results = content.optJSONArray("results");
1053+
assertNotNull(results);
1054+
assertEquals(2, results.length());
1055+
assertEquals(null, results.optJSONObject(0));
1056+
assertEquals(null, results.optJSONObject(1));
1057+
1058+
index.partialUpdateObjectsAsync(partialObjects, false, new AssertCompletionHandler() {
1059+
@Override
1060+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1061+
assertNull(error);
1062+
String taskID = content.optString("taskID");
1063+
index.waitTaskAsync(taskID, new AssertCompletionHandler() {
1064+
@Override
1065+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1066+
assertNull(error);
1067+
index.getObjectsAsync(newIds, new AssertCompletionHandler() {
1068+
@Override
1069+
public void doRequestCompleted(JSONObject content, AlgoliaException error) {
1070+
assertNotNull(content);
1071+
JSONArray results = content.optJSONArray("results");
1072+
assertNotNull(results);
1073+
assertEquals(2, results.length());
1074+
for (int i = 0; i < partialObjects.length(); ++i) {
1075+
assertEquals(partialObjects.optJSONObject(i).optString("city"), results.optJSONObject(i).optString("city"));
1076+
}
1077+
}
1078+
});
1079+
}
1080+
});
1081+
}
1082+
});
1083+
}
1084+
});
1085+
}
1086+
});
1087+
}
1088+
});
1089+
}
9141090
}

0 commit comments

Comments
 (0)