Skip to content
55 changes: 36 additions & 19 deletions src/main/java/com/coveo/pushapiclient/ApiCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,48 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

// TODO: LENS-934 - Support throttling
class ApiCore {
private final HttpClient httpClient;
private final Logger logger;
private final BackoffOptions options;

public ApiCore() {
this.httpClient = HttpClient.newHttpClient();
this.logger = LogManager.getLogger(ApiCore.class);
this(HttpClient.newHttpClient(), LogManager.getLogger(ApiCore.class));
}

public ApiCore(HttpClient httpClient, Logger logger) {
this(httpClient, logger, new BackoffOptionsBuilder().build());
}

public ApiCore(HttpClient httpClient, Logger logger, BackoffOptions options) {
this.httpClient = httpClient;
this.logger = logger;
this.options = options;
}

public HttpResponse<String> callApiWithRetries(HttpRequest request)
throws IOException, InterruptedException {
int nbRetries = 0;
long delayInMilliseconds = this.options.getRetryAfter();

while (true) {
String uri = request.uri().toString();
String reqMethod = request.method();
this.logger.debug(reqMethod + " " + uri);
HttpResponse<String> response =
this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
this.logResponse(response);

if (response != null
&& response.statusCode() == 429
&& nbRetries < this.options.getMaxRetries()) {
Thread.sleep(delayInMilliseconds);
nbRetries++;
delayInMilliseconds *= this.options.getTimeMultiple();
} else {
return response;
}
}
}

public HttpResponse<String> post(URI uri, String[] headers)
Expand All @@ -31,42 +60,30 @@ public HttpResponse<String> post(URI uri, String[] headers)

public HttpResponse<String> post(URI uri, String[] headers, BodyPublisher body)
throws IOException, InterruptedException {
this.logger.debug("POST " + uri);
HttpRequest request = HttpRequest.newBuilder().headers(headers).uri(uri).POST(body).build();
HttpResponse<String> response =
this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
this.logResponse(response);
HttpResponse<String> response = this.callApiWithRetries(request);
return response;
}

public HttpResponse<String> put(URI uri, String[] headers, BodyPublisher body)
throws IOException, InterruptedException {
this.logger.debug("PUT " + uri);
HttpRequest request = HttpRequest.newBuilder().headers(headers).uri(uri).PUT(body).build();
HttpResponse<String> response =
this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
this.logResponse(response);
HttpResponse<String> response = this.callApiWithRetries(request);
return response;
}

public HttpResponse<String> delete(URI uri, String[] headers)
throws IOException, InterruptedException {
this.logger.debug("DELETE " + uri);
HttpRequest request = HttpRequest.newBuilder().headers(headers).uri(uri).DELETE().build();
HttpResponse<String> response =
this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
this.logResponse(response);
HttpResponse<String> response = this.callApiWithRetries(request);
return response;
}

public HttpResponse<String> delete(URI uri, String[] headers, BodyPublisher body)
throws IOException, InterruptedException {
this.logger.debug("DELETE " + uri);
HttpRequest request =
HttpRequest.newBuilder().headers(headers).uri(uri).method("DELETE", body).build();
HttpResponse<String> response =
this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
this.logResponse(response);
HttpResponse<String> response = this.callApiWithRetries(request);
return response;
}

Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/coveo/pushapiclient/BackoffOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.coveo.pushapiclient;

public class BackoffOptions {
public static final int DEFAULT_RETRY_AFTER = 5000;
public static final int DEFAULT_MAX_RETRIES = 50;
public static final int DEFAULT_TIME_MULTIPLE = 2;

private final int retryAfter;
private final int maxRetries;
private final int timeMultiple;

public BackoffOptions(int retryAfter, int maxRetries, int timeMultiple) {
this.retryAfter = retryAfter;
this.maxRetries = maxRetries;
this.timeMultiple = timeMultiple;
}

public int getRetryAfter() {
return this.retryAfter;
}

public int getMaxRetries() {
return this.maxRetries;
}

public int getTimeMultiple() {
return this.timeMultiple;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.coveo.pushapiclient;

public class BackoffOptionsBuilder {

private int retryAfter = BackoffOptions.DEFAULT_RETRY_AFTER;
private int maxRetries = BackoffOptions.DEFAULT_MAX_RETRIES;
private int timeMultiple = BackoffOptions.DEFAULT_TIME_MULTIPLE;

public BackoffOptionsBuilder withRetryAfter(int retryAfter) {
this.retryAfter = retryAfter;
return this;
}

public BackoffOptionsBuilder withMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}

public BackoffOptionsBuilder withTimeMultiple(int timeMultiple) {
this.timeMultiple = timeMultiple;
return this;
}

public BackoffOptions build() {
return new BackoffOptions(this.retryAfter, this.maxRetries, this.timeMultiple);
}
}
52 changes: 49 additions & 3 deletions src/main/java/com/coveo/pushapiclient/PlatformClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ public class PlatformClient {
* @param organizationId The Coveo Organization identifier.
*/
public PlatformClient(String apiKey, String organizationId) {
this(apiKey, organizationId, new PlatformUrlBuilder().build());
this(
apiKey,
organizationId,
new PlatformUrlBuilder().build(),
new BackoffOptionsBuilder().build());
}

/**
Expand All @@ -38,9 +42,36 @@ public PlatformClient(String apiKey, String organizationId) {
* organization.
* @see <a href="https://docs.coveo.com/en/1718">Manage API Keys</a>
* @param organizationId The Coveo Organization identifier.
* @param platformUrl The PlatformUrl.
*/
public PlatformClient(String apiKey, String organizationId, PlatformUrl platformUrl) {
this(apiKey, organizationId, platformUrl, new BackoffOptionsBuilder().build());
}

/**
* Construct a PlatformClient
*
* @param apiKey An apiKey capable of pushing documents and managing sources in a Coveo
* organization.
* @see <a href="https://docs.coveo.com/en/1718">Manage API Keys</a>
* @param organizationId The Coveo Organization identifier.
* @param options The configuration options for exponential backoff
*/
public PlatformClient(String apiKey, String organizationId, BackoffOptions options) {
this(apiKey, organizationId, new PlatformUrlBuilder().build(), options);
}

/**
* Construct a PlatformClient
*
* @param apiKey An apiKey capable of pushing documents and managing sources in a Coveo
* organization.
* @see <a href="https://docs.coveo.com/en/1718">Manage API Keys</a>
* @param organizationId The Coveo Organization identifier.
* @param platformUrl The PlatformUrl.
* @param options The configuration options for exponential backoff
*/
public PlatformClient(
String apiKey, String organizationId, PlatformUrl platformUrl, BackoffOptions options) {
this.apiKey = apiKey;
this.organizationId = organizationId;
this.api = new ApiCore();
Expand All @@ -57,9 +88,24 @@ public PlatformClient(String apiKey, String organizationId, PlatformUrl platform
* @param httpClient The HttpClient.
*/
public PlatformClient(String apiKey, String organizationId, HttpClient httpClient) {
this(apiKey, organizationId, httpClient, new BackoffOptionsBuilder().build());
}

/**
* Construct a PlatformClient
*
* @param apiKey An apiKey capable of pushing documents and managing sources in a Coveo
* organization.
* @see <a href="https://docs.coveo.com/en/1718">Manage API Keys</a>
* @param organizationId The Coveo Organization identifier.
* @param httpClient The HttpClient.
* @param options The configuration options for exponential backoff
*/
public PlatformClient(
String apiKey, String organizationId, HttpClient httpClient, BackoffOptions options) {
this.apiKey = apiKey;
this.organizationId = organizationId;
this.api = new ApiCore(httpClient, LogManager.getLogger(ApiCore.class));
this.api = new ApiCore(httpClient, LogManager.getLogger(ApiCore.class), options);
this.platformUrl = new PlatformUrlBuilder().build();
}

Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/coveo/pushapiclient/PushService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ public class PushService {
private PushServiceInternal service;

public PushService(PushEnabledSource source) {
this(source, new BackoffOptionsBuilder().build());
}

public PushService(PushEnabledSource source, BackoffOptions options) {
String apiKey = source.getApiKey();
String organizationId = source.getOrganizationId();
PlatformUrl platformUrl = source.getPlatformUrl();
UploadStrategy uploader = this.getUploadStrategy();
DocumentUploadQueue queue = new DocumentUploadQueue(uploader);

this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl);
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl, options);
this.service = new PushServiceInternal(queue);
this.source = source;
}
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/coveo/pushapiclient/PushSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,53 @@ public static PushSource fromPlatformUrl(
return new PushSource(apiKey, organizationId, sourceId, platformUrl);
}

/**
* Create a Push source instance
*
* @param apiKey The API key used for all operations regarding your source.
* <p>Ensure your API key has the required privileges for the operation you will be performing
* *
* <p>For more information about which privileges are required, see <a href=
* "https://docs.coveo.com/en/1707#sources-domain">Privilege Reference.</a>
* @param organizationId The unique identifier of your organization.
* <p>The Organization Id can be retrieved in the URL of your Coveo organization.
* @param sourceId The unique identifier of the target Push source.
* <p>The Source Id can be retrieved when you edit your source in the <a href=
* "https://docs.coveo.com/en/183/glossary/coveo-administration-console">Coveo Administration
* Console</a>
* @param platformUrl The object containing additional information on the URL endpoint. You can
* use the {@link PlatformUrl} when your organization is located in a non-default Coveo
* environement and/or region. When not specified, the default platform URL values will be
* used: {@link PlatformUrl#DEFAULT_ENVIRONMENT} and {@link PlatformUrl#DEFAULT_REGION}
* * @param options The configuration options for exponential backoff
*/
public static PushSource fromPlatformUrl(
String apiKey,
String organizationId,
String sourceId,
PlatformUrl platformUrl,
BackoffOptions options) {
return new PushSource(apiKey, organizationId, sourceId, platformUrl, options);
}

private PushSource(
String apiKey, String organizationId, String sourceId, PlatformUrl platformUrl) {
this.apiKey = apiKey;
this.urlExtractor = new ApiUrl(organizationId, sourceId, platformUrl);
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl);
}

private PushSource(
String apiKey,
String organizationId,
String sourceId,
PlatformUrl platformUrl,
BackoffOptions options) {
this.apiKey = apiKey;
this.urlExtractor = new ApiUrl(organizationId, sourceId, platformUrl);
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl, options);
}

/**
* Create or update a security identity.
*
Expand Down
36 changes: 33 additions & 3 deletions src/main/java/com/coveo/pushapiclient/Source.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,48 @@ public class Source {
* @param organizationId The Coveo Organization identifier.
*/
public Source(String apiKey, String organizationId) {
this.platformClient = new PlatformClient(apiKey, organizationId);
this(apiKey, organizationId, new BackoffOptionsBuilder().build());
}

/**
* @param apiKey An apiKey capable of pushing documents and managing sources in a Coveo
* organization.
* @see <a href="https://docs.coveo.com/en/1718">Manage API Keys</a>.
* @param organizationId The Coveo Organization identifier.
* @param platformUrl
* @param options The options for exponential backoff.
*/
public Source(String apiKey, String organizationId, BackoffOptions options) {
this.platformClient = new PlatformClient(apiKey, organizationId, options);
}

/**
* @param apiKey An apiKey capable of pushing documents and managing sources in a Coveo
* organization.
* @see <a href="https://docs.coveo.com/en/1718">Manage API Keys</a>.
* @param organizationId The Coveo Organization identifier.
* @param platformUrl The object containing additional information on the URL endpoint. You can
* use the {@link PlatformUrl} when your organization is located in a non-default Coveo
* environement and/or region. When not specified, the default platform URL values will be
* used: {@link PlatformUrl#DEFAULT_ENVIRONMENT} and {@link PlatformUrl#DEFAULT_REGION}
*/
public Source(String apiKey, String organizationId, PlatformUrl platformUrl) {
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl);
this(apiKey, organizationId, platformUrl, new BackoffOptionsBuilder().build());
}

/**
* @param apiKey An apiKey capable of pushing documents and managing sources in a Coveo
* organization.
* @see <a href="https://docs.coveo.com/en/1718">Manage API Keys</a>.
* @param organizationId The Coveo Organization identifier.
* @param platformUrl The object containing additional information on the URL endpoint. You can
* use the {@link PlatformUrl} when your organization is located in a non-default Coveo
* environement and/or region. When not specified, the default platform URL values will be
* used: {@link PlatformUrl#DEFAULT_ENVIRONMENT} and {@link PlatformUrl#DEFAULT_REGION}
* @param options The configuration options for exponential backoff
*/
public Source(
String apiKey, String organizationId, PlatformUrl platformUrl, BackoffOptions options) {
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl, options);
}

/**
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/com/coveo/pushapiclient/StreamService.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ public class StreamService {
* @param source The source to which you want to send your documents.
*/
public StreamService(StreamEnabledSource source) {
this(source, new BackoffOptionsBuilder().build());
}

/**
* Creates a service to stream your documents to the provided source by interacting with the
* Stream API.
*
* <p>To perform <a href="https://docs.coveo.com/en/l62e0540">full document updates</a>, use the
* {@PushService}, since pushing documents with the {@StreamService} is equivalent to triggering a
* full source rebuild. The {@StreamService} can also be used for an initial catalog upload.
*
* @param source The source to which you want to send your documents.
* @param options The configuration options for exponential backoff
*/
public StreamService(StreamEnabledSource source, BackoffOptions options) {
String apiKey = source.getApiKey();
String organizationId = source.getOrganizationId();
PlatformUrl platformUrl = source.getPlatformUrl();
Expand All @@ -33,7 +48,7 @@ public StreamService(StreamEnabledSource source) {

this.source = source;
this.queue = new DocumentUploadQueue(uploader);
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl);
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl, options);

this.service = new StreamServiceInternal(this.source, this.queue, this.platformClient, logger);
}
Expand Down
Loading