diff --git a/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/ApiClient.java b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/ApiClient.java index f742a61a60..6782bda1a3 100644 --- a/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/ApiClient.java +++ b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/ApiClient.java @@ -254,7 +254,7 @@ public CompletableFuture executeAsync(Call call, final Type returnType) { new Callback() { @Override public void onFailure(Call call, IOException e) { - future.completeExceptionally(new AlgoliaRuntimeException(e)); + future.completeExceptionally(e.getCause()); } @Override diff --git a/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/TaskUtils.java b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/TaskUtils.java index 2f257ff130..60c63ff8ea 100644 --- a/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/TaskUtils.java +++ b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/TaskUtils.java @@ -1,8 +1,6 @@ package com.algolia.utils; import com.algolia.exceptions.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.function.IntUnaryOperator; import java.util.function.Predicate; import java.util.function.Supplier; @@ -14,22 +12,17 @@ public class TaskUtils { return Math.min(retries * 200, 5000); }; - public static void retryUntil( - Supplier> func, + public static TResponse retryUntil( + Supplier func, Predicate validate, int maxRetries, IntUnaryOperator timeout ) throws AlgoliaRuntimeException { int retryCount = 0; while (retryCount < maxRetries) { - try { - TResponse resp = func.get().get(); - if (validate.test(resp)) { - return; - } - } catch (InterruptedException | ExecutionException e) { - // if the task is interrupted, just return - return; + TResponse resp = func.get(); + if (validate.test(resp)) { + return resp; } try { Thread.sleep(timeout.applyAsInt(retryCount)); diff --git a/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/retry/ApiKeyOperation.java b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/retry/ApiKeyOperation.java new file mode 100644 index 0000000000..a1db4e6805 --- /dev/null +++ b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/retry/ApiKeyOperation.java @@ -0,0 +1,7 @@ +package com.algolia.utils; + +public enum ApiKeyOperation { + ADD, + DELETE, + UPDATE, +} diff --git a/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/retry/RetryStrategy.java b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/retry/RetryStrategy.java index d24b2710a6..8c19c3a1c0 100644 --- a/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/retry/RetryStrategy.java +++ b/clients/algoliasearch-client-java-2/algoliasearch-core/src/main/java/com/algolia/utils/retry/RetryStrategy.java @@ -35,48 +35,52 @@ public Response intercept(Chain chain) throws IOException { (useReadTransporter != null || request.method().equals("GET")) ? CallType.READ : CallType.WRITE ) .iterator(); - while (hostsIter.hasNext()) { - StatefulHost currentHost = hostsIter.next(); + try { + while (hostsIter.hasNext()) { + StatefulHost currentHost = hostsIter.next(); - // Building the request URL - HttpUrl newUrl = request.url().newBuilder().scheme(currentHost.getScheme()).host(currentHost.getHost()).build(); - request = request.newBuilder().url(newUrl).build(); + // Building the request URL + HttpUrl newUrl = request.url().newBuilder().scheme(currentHost.getScheme()).host(currentHost.getHost()).build(); + request = request.newBuilder().url(newUrl).build(); - // Computing timeout with the retry count - chain.withConnectTimeout(chain.connectTimeoutMillis() + currentHost.getRetryCount() * 1000, TimeUnit.MILLISECONDS); + // Computing timeout with the retry count + chain.withConnectTimeout(chain.connectTimeoutMillis() + currentHost.getRetryCount() * 1000, TimeUnit.MILLISECONDS); - try { - Response response = chain.proceed(request); - currentHost.setLastUse(Utils.nowUTC()); - // no timeout - if (response.isSuccessful()) { + try { + Response response = chain.proceed(request); + currentHost.setLastUse(Utils.nowUTC()); + // no timeout + if (response.isSuccessful()) { + currentHost.setUp(true); + return response; + } + if (isRetryable(response)) { + currentHost.setUp(false); + response.close(); + continue; + } + String message = response.message(); + if (response.body() != null) { + message = response.body().string(); + } + throw new AlgoliaApiException(message, response.code()); + } catch (AlgoliaApiException e) { + throw e; + } catch (SocketTimeoutException e) { + // timeout currentHost.setUp(true); - return response; + currentHost.setLastUse(Utils.nowUTC()); + currentHost.incrementRetryCount(); + } catch (UnknownHostException e) { + throw new AlgoliaApiException(e.getMessage(), 404); + } catch (Exception e) { + throw new AlgoliaApiException(e.getMessage(), 400); } - if (isRetryable(response)) { - currentHost.setUp(false); - response.close(); - continue; - } - String message = response.message(); - if (response.body() != null) { - message = response.body().string(); - } - throw new AlgoliaApiException(message, response.code()); - } catch (AlgoliaApiException e) { - throw e; - } catch (SocketTimeoutException e) { - // timeout - currentHost.setUp(true); - currentHost.setLastUse(Utils.nowUTC()); - currentHost.incrementRetryCount(); - } catch (UnknownHostException e) { - throw new AlgoliaApiException(e.getMessage(), 404); - } catch (Exception e) { - throw new AlgoliaApiException(e.getMessage(), 400); } + throw new AlgoliaRetryException("All hosts are unreachable"); + } catch (Exception e) { + throw new IOException(e); } - throw new AlgoliaRetryException("All hosts are unreachable"); } }; } diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/createRetryablePromise.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/createRetryablePromise.ts index 4c8220c4f7..67f9f3998d 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/createRetryablePromise.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/createRetryablePromise.ts @@ -11,7 +11,7 @@ export const DEFAULT_TIMEOUT = (retryCount: number): number => * @param createRetryablePromiseOptions.func - The function to run, which returns a promise. * @param createRetryablePromiseOptions.validate - The validator function. It receives the resolved return of `func`. * @param createRetryablePromiseOptions.maxRetries - The maximum number of retries. 50 by default. - * @param createRetryablePromiseOptions.timeout - The function to decide how long to wait between tries. + * @param createRetryablePromiseOptions.timeout - The function to decide how long to wait between retries. */ export function createRetryablePromise({ func, diff --git a/clients/algoliasearch-client-javascript/packages/client-common/src/types/CreateRetryablePromise.ts b/clients/algoliasearch-client-javascript/packages/client-common/src/types/CreateRetryablePromise.ts index 5599a66665..209b0515f2 100644 --- a/clients/algoliasearch-client-javascript/packages/client-common/src/types/CreateRetryablePromise.ts +++ b/clients/algoliasearch-client-javascript/packages/client-common/src/types/CreateRetryablePromise.ts @@ -15,7 +15,7 @@ export type CreateRetryablePromiseOptions = { maxRetries?: number; /** - * The function to decide how long to wait between tries. + * The function to decide how long to wait between retries. */ timeout?: (retryCount: number) => number; }; diff --git a/templates/java/libraries/okhttp-gson/api.mustache b/templates/java/libraries/okhttp-gson/api.mustache index 3a41d6826b..d906448fd4 100644 --- a/templates/java/libraries/okhttp-gson/api.mustache +++ b/templates/java/libraries/okhttp-gson/api.mustache @@ -278,24 +278,229 @@ public class {{classname}} extends ApiClient { {{/operation}} {{#isSearchClient}} - public void waitForTask(String indexName, Long taskID, RequestOptions requestOptions, int maxRetries, IntUnaryOperator timeout) { + /** + * Helper: Wait for a task to complete with `indexName` and `taskID`. + * + * @summary Wait for a task to complete. + * @param indexName The `indexName` where the operation was performed. + * @param taskID The `taskID` returned in the method response. + * @param maxRetries The maximum number of retry. 50 by default. (optional) + * @param timeout The function to decide how long to wait between retries. min(retries * 200, 5000) by default. (optional) + * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional) + */ + public void waitForTask(String indexName, Long taskID, int maxRetries, IntUnaryOperator timeout, RequestOptions requestOptions) { TaskUtils.retryUntil(() -> { - return this.getTaskAsync(indexName, taskID, requestOptions); + return this.getTask(indexName, taskID, requestOptions); }, (GetTaskResponse task) -> { return task.getStatus() == TaskStatus.PUBLISHED; }, maxRetries, timeout); } + /** + * Helper: Wait for a task to complete with `indexName` and `taskID`. + * + * @summary Wait for a task to complete. + * @param indexName The `indexName` where the operation was performed. + * @param taskID The `taskID` returned in the method response. + * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional) + */ public void waitForTask(String indexName, Long taskID, RequestOptions requestOptions) { - this.waitForTask(indexName, taskID, requestOptions, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT); + this.waitForTask(indexName, taskID, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, requestOptions); } + /** + * Helper: Wait for a task to complete with `indexName` and `taskID`. + * + * @summary Wait for a task to complete. + * @param indexName The `indexName` where the operation was performed. + * @param taskID The `taskID` returned in the method response. + * @param maxRetries The maximum number of retry. 50 by default. (optional) + * @param timeout The function to decide how long to wait between retries. min(retries * 200, 5000) by default. (optional) + */ public void waitForTask(String indexName, Long taskID, int maxRetries, IntUnaryOperator timeout) { - this.waitForTask(indexName, taskID, null, maxRetries, timeout); + this.waitForTask(indexName, taskID, maxRetries, timeout, null); } + /** + * Helper: Wait for a task to complete with `indexName` and `taskID`. + * + * @summary Wait for a task to complete. + * @param indexName The `indexName` where the operation was performed. + * @param taskID The `taskID` returned in the method response. + */ public void waitForTask(String indexName, Long taskID) { - this.waitForTask(indexName, taskID, null, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT); + this.waitForTask(indexName, taskID, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, null); + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. + * @param key The `key` that has been added, deleted or updated. + * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of the response with it. + * @param maxRetries The maximum number of retry. 50 by default. (optional) + * @param timeout The function to decide how long to wait between retries. min(retries * 200, 5000) by default. (optional) + * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional) + */ + public Key waitForApiKey( + ApiKeyOperation operation, + String key, + ApiKey apiKey, + int maxRetries, + IntUnaryOperator timeout, + RequestOptions requestOptions + ) { + if (operation == ApiKeyOperation.UPDATE) { + if (apiKey == null) { + throw new AlgoliaRetryException("`apiKey` is required when waiting for an `update` operation."); + } + + // when updating an api key, we poll the api until we receive a different key + return TaskUtils.retryUntil( + () -> { + return this.getApiKey(key, requestOptions); + }, + (Key respKey) -> { + // we need to convert to an ApiKey object to use the `equals` method + ApiKey sameType = new ApiKey() + .setAcl(respKey.getAcl()) + .setDescription(respKey.getDescription()) + .setIndexes(respKey.getIndexes()) + .setMaxHitsPerQuery(respKey.getMaxHitsPerQuery()) + .setMaxQueriesPerIPPerHour(respKey.getMaxQueriesPerIPPerHour()) + .setQueryParameters(respKey.getQueryParameters()) + .setReferers(respKey.getReferers()) + .setValidity(respKey.getValidity()); + + return apiKey.equals(sameType); + }, + maxRetries, + timeout + ); + } + + // bypass lambda restriction to modify final object + final Key[] addedKey = new Key[] { null }; + + // check the status of the getApiKey method + TaskUtils.retryUntil( + () -> { + try { + addedKey[0] = this.getApiKey(key, requestOptions); + // magic number to signify we found the key + return -2; + } catch (AlgoliaApiException e) { + return e.getHttpErrorCode(); + } + }, + (Integer status) -> { + switch (operation) { + case ADD: + // stop either when the key is created or when we don't receive 404 + return status == -2 || status != 404; + case DELETE: + // stop when the key is not found + return status == 404; + default: + // continue + return false; + } + }, + maxRetries, + timeout + ); + + return addedKey[0]; + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. (ADD or DELETE only) + * @param key The `key` that has been added, deleted or updated. + * @param maxRetries The maximum number of retry. 50 by default. (optional) + * @param timeout The function to decide how long to wait between retries. min(retries * 200, 5000) by default. (optional) + * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional) + */ + public Key waitForApiKey(ApiKeyOperation operation, String key, int maxRetries, IntUnaryOperator timeout, RequestOptions requestOptions) { + return this.waitForApiKey(operation, key, null, maxRetries, timeout, requestOptions); + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. + * @param key The `key` that has been added, deleted or updated. + * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of the response with it. + * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional) + */ + public Key waitForApiKey(ApiKeyOperation operation, String key, ApiKey apiKey, RequestOptions requestOptions) { + return this.waitForApiKey(operation, key, apiKey, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, requestOptions); + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. (ADD or DELETE only) + * @param key The `key` that has been added, deleted or updated. + * @param requestOptions The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional) + */ + public Key waitForApiKey(ApiKeyOperation operation, String key, RequestOptions requestOptions) { + return this.waitForApiKey(operation, key, null, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, requestOptions); + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. + * @param key The `key` that has been added, deleted or updated. + * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of the response with it. + * @param maxRetries The maximum number of retry. 50 by default. (optional) + * @param timeout The function to decide how long to wait between retries. min(retries * 200, 5000) by default. (optional) + */ + public Key waitForApiKey(ApiKeyOperation operation, String key, ApiKey apiKey, int maxRetries, IntUnaryOperator timeout) { + return this.waitForApiKey(operation, key, apiKey, maxRetries, timeout, null); + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. (ADD or DELETE only) + * @param key The `key` that has been added, deleted or updated. + * @param maxRetries The maximum number of retry. 50 by default. (optional) + * @param timeout The function to decide how long to wait between retries. min(retries * 200, 5000) by default. (optional) + */ + public Key waitForApiKey(ApiKeyOperation operation, String key, int maxRetries, IntUnaryOperator timeout) { + return this.waitForApiKey(operation, key, null, maxRetries, timeout, null); + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. + * @param key The `key` that has been added, deleted or updated. + * @param apiKey Necessary to know if an `update` operation has been processed, compare fields of the response with it. + */ + public Key waitForApiKey(ApiKeyOperation operation, String key, ApiKey apiKey) { + return this.waitForApiKey(operation, key, apiKey, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, null); + } + + /** + * Helper: Wait for an API key to be added, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param operation The `operation` that was done on a `key`. (ADD or DELETE only) + * @param key The `key` that has been added, deleted or updated. + */ + public Key waitForApiKey(ApiKeyOperation operation, String key) { + return this.waitForApiKey(operation, key, null, TaskUtils.DEFAULT_MAX_RETRIES, TaskUtils.DEFAULT_TIMEOUT, null); } {{/isSearchClient}} } diff --git a/templates/java/pojo.mustache b/templates/java/pojo.mustache index 717b7e8901..0d5a4a44db 100644 --- a/templates/java/pojo.mustache +++ b/templates/java/pojo.mustache @@ -24,7 +24,7 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ @SerializedName("{{baseName}}") {{/gson}} {{#isContainer}} - private {{{datatypeWithEnum}}} {{name}}; + private {{{datatypeWithEnum}}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}; {{/isContainer}} {{^isContainer}} {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}; diff --git a/website/docs/clients/guides/wait-for-a-task-to-finish.mdx b/website/docs/clients/guides/wait-for-a-task-to-finish.mdx index 092002fd4e..9e5281e205 100644 --- a/website/docs/clients/guides/wait-for-a-task-to-finish.mdx +++ b/website/docs/clients/guides/wait-for-a-task-to-finish.mdx @@ -35,7 +35,7 @@ await client.waitForTask({ taskID, // Number of maximum retries to do maxRetries: 100, - // The time to wait between tries + // The time to wait between retries timeout: (retryCount: number): number => Math.min(retryCount * 200, 5000), }); ``` @@ -68,7 +68,7 @@ Algolia\AlgoliaSearch\Support\Helpers::retryUntil( }, 100, // Number of maximum retries to do 5000, // Default timeout - // Calculation of the time to wait between tries + // Calculation of the time to wait between retries function ($timeout, $retryCount) { return min($retryCount * 200, $timeout) * 1000; } @@ -97,7 +97,7 @@ await client.waitForTask( response.getTaskID(), // Number of maximum retries to do 100, - // The time to wait between tries + // The time to wait between retries retryCount -> { return Math.min(retryCount * 200, 5000); }