diff --git a/src/main/java/com/coveo/pushapiclient/PlatformClient.java b/src/main/java/com/coveo/pushapiclient/PlatformClient.java index 22a2d067..8523a5d5 100644 --- a/src/main/java/com/coveo/pushapiclient/PlatformClient.java +++ b/src/main/java/com/coveo/pushapiclient/PlatformClient.java @@ -21,6 +21,7 @@ public class PlatformClient { private final String organizationId; private final ApiCore api; private final PlatformUrl platformUrl; + private String[] userAgents; /** * Construct a PlatformClient @@ -570,13 +571,12 @@ private String[] getAuthorizationHeader() { } private String[] getContentTypeApplicationJSONHeader() { - MavenXpp3Reader reader = new MavenXpp3Reader(); - String version = ""; - try { - Model model = reader.read(new FileReader("pom.xml")); - version = model.getVersion(); - } catch (Exception e) { - version = "Not-Available"; + StringBuilder userAgentValue = new StringBuilder(); + String sdkVersion = getSdkVersion(); + userAgentValue.append(String.format("CoveoSDKJava/%s", sdkVersion)); + + if (userAgents != null && userAgents.length > 0) { + userAgentValue.append(" ").append(String.join(" ", userAgents)); } return new String[] { @@ -585,10 +585,20 @@ private String[] getContentTypeApplicationJSONHeader() { "Accept", "application/json", "User-Agent", - String.format("CoveoSDKJava/%s", version) + userAgentValue.toString() }; } + private String getSdkVersion() { + MavenXpp3Reader reader = new MavenXpp3Reader(); + try { + Model model = reader.read(new FileReader("pom.xml")); + return model.getVersion(); + } catch (Exception e) { + return "Not-Available"; + } + } + private String[] getAes256Header() { return new String[] {"x-amz-server-side-encryption", "AES256"}; } @@ -600,4 +610,20 @@ private String[] getContentTypeApplicationOctetStreamHeader() { private String toJSON(HashMap hashMap) { return new Gson().toJson(hashMap, new TypeToken>() {}.getType()); } + + public String[] getUserAgents() { + return userAgents; + } + + public void setUserAgents(String[] userAgents) { + if (!validUserAgents(userAgents)) { + throw new IllegalArgumentException("Invalid user agents"); + } + this.userAgents = userAgents; + } + + protected boolean validUserAgents(String[] userAgents) { + String pattern = "^.+/v(\\d+(\\.\\d+){0,2})$"; + return Arrays.stream(userAgents).allMatch(agent -> agent.matches(pattern)); + } } diff --git a/src/main/java/com/coveo/pushapiclient/StreamService.java b/src/main/java/com/coveo/pushapiclient/StreamService.java index 525e2941..eda7bcb3 100644 --- a/src/main/java/com/coveo/pushapiclient/StreamService.java +++ b/src/main/java/com/coveo/pushapiclient/StreamService.java @@ -14,6 +14,22 @@ public class StreamService { private String streamId; private DocumentUploadQueue queue; + /** + * Creates a service to stream your documents to the provided source by interacting with the + * Stream API. + * + *

To perform full document updates or + * deletions, use the {@UpdateStreamService}, 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 userAgents The user agent to use for the requests. + */ + public StreamService(StreamEnabledSource source, String[] userAgents) { + this(source, new BackoffOptionsBuilder().build(), userAgents); + } + /** * Creates a service to stream your documents to the provided source by interacting with the * Stream API. @@ -42,6 +58,23 @@ public StreamService(StreamEnabledSource source) { * @param options The configuration options for exponential backoff. */ public StreamService(StreamEnabledSource source, BackoffOptions options) { + this(source, options, null); + } + + /** + * Creates a service to stream your documents to the provided source by interacting with the + * Stream API. + * + *

To perform full document updates or + * deletions, use the {@UpdateStreamService}, 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. + * @param userAgents The user agent to use for the requests. + */ + public StreamService(StreamEnabledSource source, BackoffOptions options, String[] userAgents) { String apiKey = source.getApiKey(); String organizationId = source.getOrganizationId(); PlatformUrl platformUrl = source.getPlatformUrl(); @@ -51,7 +84,7 @@ public StreamService(StreamEnabledSource source, BackoffOptions options) { this.source = source; this.queue = new DocumentUploadQueue(uploader); this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl, options); - + platformClient.setUserAgents(userAgents); this.service = new StreamServiceInternal(this.source, this.queue, this.platformClient, logger); } diff --git a/src/main/java/com/coveo/pushapiclient/UpdateStreamService.java b/src/main/java/com/coveo/pushapiclient/UpdateStreamService.java index 7e25028e..4500d11f 100644 --- a/src/main/java/com/coveo/pushapiclient/UpdateStreamService.java +++ b/src/main/java/com/coveo/pushapiclient/UpdateStreamService.java @@ -14,6 +14,21 @@ public class UpdateStreamService { private FileContainer fileContainer; + /** + * Creates a service to stream your documents to the provided source by interacting with the + * Stream API. This provides the ability to incrementally add, update, or delete documents via a + * stream. + * + *

To perform a full source rebuild, use the + * {@StreamService} + * + * @param source The source to which you want to send your documents. + * @param userAgents The user agent to use for the requests. + */ + public UpdateStreamService(StreamEnabledSource source, String[] userAgents) { + this(source, new BackoffOptionsBuilder().build(), userAgents); + } + /** * Creates a service to stream your documents to the provided source by interacting with the * Stream API. This provides the ability to incrementally add, update, or delete documents via a @@ -40,10 +55,28 @@ public UpdateStreamService(StreamEnabledSource source) { * @param options The configuration options for exponential backoff. */ public UpdateStreamService(StreamEnabledSource source, BackoffOptions options) { + this(source, options, null); + } + + /** + * Creates a service to stream your documents to the provided source by interacting with the + * Stream API. This provides the ability to incrementally add, update, or delete documents via a + * stream. + * + *

To perform a full source rebuild, use the + * {@StreamService} + * + * @param source The source to which you want to send your documents. + * @param options The configuration options for exponential backoff. + * @param userAgents The user agent to use for the requests. + */ + public UpdateStreamService( + StreamEnabledSource source, BackoffOptions options, String[] userAgents) { Logger logger = LogManager.getLogger(UpdateStreamService.class); this.platformClient = new PlatformClient( source.getApiKey(), source.getOrganizationId(), source.getPlatformUrl(), options); + this.platformClient.setUserAgents(userAgents); this.updateStreamServiceInternal = new UpdateStreamServiceInternal( source, diff --git a/src/test/java/com/coveo/pushapiclient/PlatformClientTest.java b/src/test/java/com/coveo/pushapiclient/PlatformClientTest.java index 5571ce18..0d774e0e 100644 --- a/src/test/java/com/coveo/pushapiclient/PlatformClientTest.java +++ b/src/test/java/com/coveo/pushapiclient/PlatformClientTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.verify; import com.google.gson.Gson; +import java.io.FileReader; import java.io.IOException; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -14,6 +15,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import org.apache.maven.model.Model; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -39,6 +43,10 @@ public void assertApplicationJsonHeader() { assertTrue(this.argument.getValue().headers().map().get("Accept").contains("application/json")); } + public void assertUserAgentHeader(String userAgentValue) { + assertTrue(this.argument.getValue().headers().map().get("User-Agent").contains(userAgentValue)); + } + public SecurityIdentityModel securityIdentityModel() { return new SecurityIdentityModel(identityModels(), identityModel(), identityModels()); } @@ -472,4 +480,50 @@ public void testDeleteDocument() throws IOException, InterruptedException { assertApplicationJsonHeader(); assertAuthorizationHeader(); } + + @Test + public void testCorrectUserAgentHeader() + throws IOException, InterruptedException, XmlPullParserException { + String[] userAgents = {"MyAgent/v1", "MyAgent/v2.1", "MyAgent/v3.1.1"}; + String version = getVersionFromPom(); + String defaultAgent = String.format("CoveoSDKJava/%s", version); + String[] userAgentsWithDefault = new String[userAgents.length + 1]; + userAgentsWithDefault[0] = defaultAgent; + System.arraycopy(userAgents, 0, userAgentsWithDefault, 1, userAgents.length); + + client.setUserAgents(userAgents); + client.createSource("the_name", SourceType.PUSH, SourceVisibility.SECURED); + verify(httpClient) + .send(argument.capture(), any(HttpResponse.BodyHandlers.ofString().getClass())); + + assertUserAgentHeader(String.join(" ", userAgentsWithDefault)); + } + + @Test + public void testDefaultUserAgentHeader() + throws IOException, InterruptedException, XmlPullParserException { + client.createSource("the_name", SourceType.PUSH, SourceVisibility.SECURED); + verify(httpClient) + .send(argument.capture(), any(HttpResponse.BodyHandlers.ofString().getClass())); + String version = getVersionFromPom(); + assertUserAgentHeader(String.format("CoveoSDKJava/%s", version)); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidHeaderValue() { + String[] userAgents = {"MyAgent/v1", "MyAgent/v2.1", "MyAgent/v3.1.1", "invalidHeaderValue"}; + client.setUserAgents(userAgents); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidSemanticVersionHeaderValue() { + String[] userAgents = {"MyAgent/v1.1.1.1"}; + client.setUserAgents(userAgents); + } + + private String getVersionFromPom() throws IOException, XmlPullParserException { + MavenXpp3Reader reader = new MavenXpp3Reader(); + Model model = reader.read(new FileReader("pom.xml")); + return model.getVersion(); + } }