Skip to content

Commit c8efa23

Browse files
authored
feat(java): add bridge to transformation on search (#4939)
1 parent 453dc2e commit c8efa23

File tree

9 files changed

+244
-7
lines changed

9 files changed

+244
-7
lines changed

clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/ApiClient.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public abstract class ApiClient implements Closeable {
2828

2929
private final Requester requester;
3030
private final ExecutorService executor;
31-
private AuthInterceptor authInterceptor;
31+
public final ClientOptions clientOptions;
32+
public AuthInterceptor authInterceptor;
3233

3334
/** Constructs a new instance of the {@link ApiClient}. */
3435
protected ApiClient(
@@ -47,9 +48,9 @@ protected ApiClient(
4748
if (apiKey == null || apiKey.isEmpty()) {
4849
throw new AlgoliaRuntimeException("`apiKey` is missing.");
4950
}
50-
final ClientOptions clientOptions = options != null ? options : new ClientOptions();
51-
this.executor = clientOptions.getExecutor();
52-
this.requester = clientOptions.getCustomRequester() != null
51+
clientOptions = options != null ? options : new ClientOptions();
52+
executor = clientOptions.getExecutor();
53+
requester = clientOptions.getCustomRequester() != null
5354
? clientOptions.getCustomRequester()
5455
: defaultRequester(appId, apiKey, clientName, clientOptions, defaultHosts, connectTimeout, readTimeout, writeTimeout);
5556
}

clients/algoliasearch-client-java/algoliasearch/src/main/java/com/algolia/internal/interceptors/AuthInterceptor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ public void setApiKey(String apiKey) {
2323
this.apiKey = apiKey;
2424
}
2525

26+
public String getApiKey() {
27+
return this.apiKey;
28+
}
29+
30+
public String getApplicationId() {
31+
return this.applicationId;
32+
}
33+
2634
@Nonnull
2735
@Override
2836
public Response intercept(Chain chain) throws IOException {

scripts/cts/runCts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export async function runCts(
158158
assertValidReplaceAllObjectsFailed(languages.length - skip('dart'));
159159
assertValidReplaceAllObjectsScopes(languages.length - skip('dart'));
160160
assertValidWaitForApiKey(languages.length - skip('dart'));
161-
assertPushMockValid(only('javascript') + only('go') + only('python'));
161+
assertPushMockValid(only('javascript') + only('go') + only('python') + only('java'));
162162
}
163163
if (withBenchmarkServer) {
164164
printBenchmarkReport();

specs/search/helpers/partialUpdateObjectsWithTransformation.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ method:
55
- javascript
66
- go
77
- python
8+
- java
89
tags:
910
- Records
1011
operationId: partialUpdateObjectsWithTransformation

specs/search/helpers/saveObjectsWithTransformation.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ method:
55
- javascript
66
- go
77
- python
8+
- java
89
tags:
910
- Records
1011
operationId: saveObjectsWithTransformation

templates/java/api.mustache

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {{invokerPackage}}.ApiClient;
66
import {{invokerPackage}}.config.ClientOptions;
77

88
import com.fasterxml.jackson.core.type.TypeReference;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
910
import javax.annotation.Nullable;
1011

1112
import okhttp3.Call;
@@ -45,10 +46,21 @@ import java.util.Base64;
4546
import javax.annotation.Nonnull;
4647
import javax.crypto.Mac;
4748
import javax.crypto.spec.SecretKeySpec;
49+
import com.algolia.model.ingestion.PushTaskPayload;
50+
import com.algolia.model.ingestion.PushTaskRecords;
51+
import com.algolia.model.ingestion.WatchResponse;
4852
{{/isSearchClient}}
4953

5054
{{#operations}}
5155
public class {{classname}} extends ApiClient {
56+
{{#isSearchClient}}
57+
private IngestionClient ingestionTransporter;
58+
59+
public void setTransformationRegion(String region) {
60+
this.ingestionTransporter = new IngestionClient(this.authInterceptor.getApplicationId(), this.authInterceptor.getApiKey(), region, this.clientOptions);
61+
}
62+
{{/isSearchClient}}
63+
5264
{{#hasRegionalHost}}
5365
private static final String[] allowedRegions = { {{#allowedRegions}}"{{.}}"{{^-last}},{{/-last}}{{/allowedRegions}} };
5466

@@ -243,4 +255,4 @@ public class {{classname}} extends ApiClient {
243255

244256
{{> api_helpers}}
245257
}
246-
{{/operations}}
258+
{{/operations}}

templates/java/api_helpers.mustache

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,108 @@ public <T> List<BatchResponse> chunkedBatch(
653653
return chunkedBatch(indexName, objects, action, waitForTasks, 1000, requestOptions);
654654
}
655655
656+
/**
657+
* Helper: Similar to the `saveObjects` method but requires a Push connector
658+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
659+
* to be created first, in order to transform records before indexing them to Algolia. The
660+
* `region` must have been passed to the client instantiation method.
661+
*
662+
* @param indexName The `indexName` to replace `objects` in.
663+
* @param objects The array of `objects` to store in the given Algolia `indexName`.
664+
* @throws AlgoliaRetryException When the retry has failed on all hosts
665+
* @throws AlgoliaApiException When the API sends an http error code
666+
* @throws AlgoliaRuntimeException When an error occurred during the serialization
667+
*/
668+
public <T> WatchResponse saveObjectsWithTransformation(String indexName, Iterable<T> objects) {
669+
return saveObjectsWithTransformation(indexName, objects, null);
670+
}
671+
672+
/**
673+
* Helper: Similar to the `saveObjects` method but requires a Push connector
674+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
675+
* to be created first, in order to transform records before indexing them to Algolia. The
676+
* `region` must have been passed to the client instantiation method.
677+
*
678+
* @param indexName The `indexName` to replace `objects` in.
679+
* @param objects The array of `objects` to store in the given Algolia `indexName`.
680+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
681+
* the transporter requestOptions. (optional)
682+
*/
683+
public <T> WatchResponse saveObjectsWithTransformation(String indexName, Iterable<T> objects, RequestOptions requestOptions) {
684+
return saveObjectsWithTransformation(indexName, objects, false, requestOptions);
685+
}
686+
687+
/**
688+
* Helper: Similar to the `saveObjects` method but requires a Push connector
689+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
690+
* to be created first, in order to transform records before indexing them to Algolia. The
691+
* `region` must have been passed to the client instantiation method.
692+
*
693+
* @param indexName The `indexName` to replace `objects` in.
694+
* @param objects The array of `objects` to store in the given Algolia `indexName`.
695+
* @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
696+
* processed, this operation may slow the total execution time of this method but is more
697+
* reliable.
698+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
699+
* the transporter requestOptions. (optional)
700+
*/
701+
public <T> WatchResponse saveObjectsWithTransformation(
702+
String indexName,
703+
Iterable<T> objects,
704+
boolean waitForTasks,
705+
RequestOptions requestOptions
706+
) {
707+
return saveObjectsWithTransformation(indexName, objects, waitForTasks, 1000, requestOptions);
708+
}
709+
710+
/**
711+
* Helper: Similar to the `saveObjects` method but requires a Push connector
712+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
713+
* to be created first, in order to transform records before indexing them to Algolia. The
714+
* `region` must have been passed to the client instantiation method.
715+
*
716+
* @param indexName The `indexName` to replace `objects` in.
717+
* @param objects The array of `objects` to store in the given Algolia `indexName`.
718+
* @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
719+
* processed, this operation may slow the total execution time of this method but is more
720+
* reliable.
721+
* @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal
722+
* to `length(objects) / batchSize`.
723+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
724+
* the transporter requestOptions. (optional)
725+
*/
726+
public <T> WatchResponse saveObjectsWithTransformation(
727+
String indexName,
728+
Iterable<T> objects,
729+
boolean waitForTasks,
730+
int batchSize,
731+
RequestOptions requestOptions
732+
) {
733+
if (this.ingestionTransporter == null) {
734+
throw new AlgoliaRuntimeException("`setTransformationRegion` must have been called before calling this method.");
735+
}
736+
737+
return this.ingestionTransporter.push(
738+
indexName,
739+
new PushTaskPayload().setAction(com.algolia.model.ingestion.Action.ADD_OBJECT).setRecords(this.objectsToPushTaskRecords(objects)),
740+
waitForTasks,
741+
requestOptions
742+
);
743+
}
744+
745+
private <T> List<PushTaskRecords> objectsToPushTaskRecords(Iterable<T> objects) {
746+
try {
747+
ObjectMapper mapper = new ObjectMapper();
748+
String json = mapper.writeValueAsString(objects);
749+
750+
return mapper.readValue(json, new TypeReference<List<PushTaskRecords>>() {});
751+
} catch (Exception e) {
752+
throw new AlgoliaRuntimeException(
753+
"each object must have an `objectID` key in order to be used with the" + " WithTransformation methods"
754+
);
755+
}
756+
}
757+
656758
/**
657759
* Helper: Saves the given array of objects in the given index. The `chunkedBatch` helper is used
658760
* under the hood, which creates a `batch` requests with at most 1000 objects in it.
@@ -777,6 +879,114 @@ public List<BatchResponse> deleteObjects(String indexName, List<String> objectID
777879
return chunkedBatch(indexName, objects, Action.DELETE_OBJECT, waitForTasks, batchSize, requestOptions);
778880
}
779881
882+
/**
883+
* Helper: Similar to the `partialUpdateObjects` method but requires a Push connector
884+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
885+
* to be created first, in order to transform records before indexing them to Algolia. The
886+
* `region` must have been passed to the client instantiation method.
887+
*
888+
* @param indexName The `indexName` to update `objects` in.
889+
* @param objects The array of `objects` to update in the given Algolia `indexName`.
890+
* @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
891+
* will fail.
892+
*/
893+
public <T> WatchResponse partialUpdateObjectsWithTransformation(String indexName, Iterable<T> objects, boolean createIfNotExists) {
894+
return partialUpdateObjectsWithTransformation(indexName, objects, createIfNotExists, false, null);
895+
}
896+
897+
/**
898+
* Helper: Similar to the `partialUpdateObjects` method but requires a Push connector
899+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
900+
* to be created first, in order to transform records before indexing them to Algolia. The
901+
* `region` must have been passed to the client instantiation method.
902+
*
903+
* @param indexName The `indexName` to update `objects` in.
904+
* @param objects The array of `objects` to update in the given Algolia `indexName`.
905+
* @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
906+
* will fail.
907+
* @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
908+
* processed, this operation may slow the total execution time of this method but is more
909+
* reliable.
910+
*/
911+
public <T> WatchResponse partialUpdateObjectsWithTransformation(
912+
String indexName,
913+
Iterable<T> objects,
914+
boolean createIfNotExists,
915+
boolean waitForTasks
916+
) {
917+
return partialUpdateObjectsWithTransformation(indexName, objects, createIfNotExists, waitForTasks, null);
918+
}
919+
920+
/**
921+
* Helper: Similar to the `partialUpdateObjects` method but requires a Push connector
922+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
923+
* to be created first, in order to transform records before indexing them to Algolia. The
924+
* `region` must have been passed to the client instantiation method.
925+
*
926+
* @param indexName The `indexName` to update `objects` in.
927+
* @param objects The array of `objects` to update in the given Algolia `indexName`.
928+
* @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
929+
* will fail.
930+
* @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
931+
* processed, this operation may slow the total execution time of this method but is more
932+
* reliable.
933+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
934+
* the transporter requestOptions. (optional)
935+
*/
936+
public <T> WatchResponse partialUpdateObjectsWithTransformation(
937+
String indexName,
938+
Iterable<T> objects,
939+
boolean createIfNotExists,
940+
boolean waitForTasks,
941+
RequestOptions requestOptions
942+
) {
943+
return partialUpdateObjectsWithTransformation(indexName, objects, createIfNotExists, waitForTasks, 1000, null);
944+
}
945+
946+
/**
947+
* Helper: Similar to the `partialUpdateObjects` method but requires a Push connector
948+
* (https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push/)
949+
* to be created first, in order to transform records before indexing them to Algolia. The
950+
* `region` must have been passed to the client instantiation method.
951+
*
952+
* @param indexName The `indexName` to update `objects` in.
953+
* @param objects The array of `objects` to update in the given Algolia `indexName`.
954+
* @param createIfNotExists To be provided if non-existing objects are passed, otherwise, the call
955+
* will fail.
956+
* @param waitForTasks - Whether or not we should wait until every `batch` tasks has been
957+
* processed, this operation may slow the total execution time of this method but is more
958+
* reliable.
959+
* @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal
960+
* to `length(objects) / batchSize`.
961+
* @param requestOptions The requestOptions to send along with the query, they will be merged with
962+
* the transporter requestOptions. (optional)
963+
*/
964+
public <T> WatchResponse partialUpdateObjectsWithTransformation(
965+
String indexName,
966+
Iterable<T> objects,
967+
boolean createIfNotExists,
968+
boolean waitForTasks,
969+
int batchSize,
970+
RequestOptions requestOptions
971+
) {
972+
if (this.ingestionTransporter == null) {
973+
throw new AlgoliaRuntimeException("`setTransformationRegion` must have been called before calling this method.");
974+
}
975+
976+
return this.ingestionTransporter.push(
977+
indexName,
978+
new PushTaskPayload()
979+
.setAction(
980+
createIfNotExists
981+
? com.algolia.model.ingestion.Action.PARTIAL_UPDATE_OBJECT
982+
: com.algolia.model.ingestion.Action.PARTIAL_UPDATE_OBJECT_NO_CREATE
983+
)
984+
.setRecords(this.objectsToPushTaskRecords(objects)),
985+
waitForTasks,
986+
requestOptions
987+
);
988+
}
989+
780990
/**
781991
* Helper: Replaces object content of all the given objects according to their respective
782992
* `objectID` field. The `chunkedBatch` helper is used under the hood, which creates a `batch`

templates/java/tests/client/client.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import com.algolia.EchoInterceptor;
1010
import com.algolia.EchoResponse;
1111
import com.algolia.api.{{client}};
1212
import com.algolia.model.{{import}}.*;
13+
{{#isSearchClient}}
14+
import com.algolia.model.ingestion.WatchResponse;
15+
{{/isSearchClient}}
1316
import com.algolia.config.*;
1417
import java.util.*;
1518
import java.time.Duration;
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
{{^autoCreateClient}}{{client}} client = {{/autoCreateClient}}new {{client}}("{{parametersWithDataTypeMap.appId.value}}","{{parametersWithDataTypeMap.apiKey.value}}"{{#hasRegionalHost}}{{#parametersWithDataTypeMap.region}},"{{parametersWithDataTypeMap.region.value}}"{{/parametersWithDataTypeMap.region}}{{/hasRegionalHost}}{{#useEchoRequester}},withEchoRequester(){{/useEchoRequester}}{{#hasCustomHosts}},withCustomHosts(Arrays.asList({{#customHosts}}new Host("true".equals(System.getenv("CI")) ? "localhost" : "host.docker.internal", EnumSet.of(CallType.READ, CallType.WRITE), "http", {{port}}){{^-last}},{{/-last}}{{/customHosts}}), {{gzipEncoding}}){{/hasCustomHosts}});
1+
{{^autoCreateClient}}{{client}} client = {{/autoCreateClient}}new {{client}}("{{parametersWithDataTypeMap.appId.value}}","{{parametersWithDataTypeMap.apiKey.value}}"{{#hasRegionalHost}}{{#parametersWithDataTypeMap.region}},"{{parametersWithDataTypeMap.region.value}}"{{/parametersWithDataTypeMap.region}}{{/hasRegionalHost}}{{#useEchoRequester}},withEchoRequester(){{/useEchoRequester}}{{#hasCustomHosts}},withCustomHosts(Arrays.asList({{#customHosts}}new Host("true".equals(System.getenv("CI")) ? "localhost" : "host.docker.internal", EnumSet.of(CallType.READ, CallType.WRITE), "http", {{port}}){{^-last}},{{/-last}}{{/customHosts}}), {{gzipEncoding}}){{/hasCustomHosts}});
2+
{{#hasTransformationRegion}}client.setTransformationRegion("{{{transformationRegion}}}");{{/hasTransformationRegion}}

0 commit comments

Comments
 (0)