diff --git a/src/main/java/com/coveo/pushapiclient/ApiUrl.java b/src/main/java/com/coveo/pushapiclient/ApiUrl.java new file mode 100644 index 00000000..b416576d --- /dev/null +++ b/src/main/java/com/coveo/pushapiclient/ApiUrl.java @@ -0,0 +1,122 @@ +package com.coveo.pushapiclient; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Private util class to extract dynamic parts from a API URL + * Handles extraction of identifiers and platform URL from a source URL. + * + * @See https://docs.coveo.com/en/1546#push-api-url + * https://docs.coveo.com/en/3295#stream-api-url + */ +class ApiUrl { + private final String organizationId; + private final String sourceId; + private final PlatformUrl platformUrl; + private final String sourceUrl; + + public ApiUrl(URL sourceUrl) throws MalformedURLException { + List identifiers = this.extractIdentifiers(sourceUrl); + this.organizationId = identifiers.get(0); + this.sourceId = identifiers.get(1); + this.sourceUrl = sourceUrl.toString(); + this.platformUrl = this.extractPlatformUrl(sourceUrl); + } + + public ApiUrl(String organizationId, String sourceId, PlatformUrl platformUrl) { + this.organizationId = organizationId; + this.sourceId = sourceId; + this.platformUrl = platformUrl; + this.sourceUrl = String.format("https://api.cloud.coveo.com/push/v1/organizations/%s/sources/%s", + this.organizationId, this.sourceId); + } + + public String getUrl() { + return this.sourceUrl; + } + + public String getOrganizationId() { + return this.organizationId; + } + + public String getSourceId() { + return this.sourceId; + } + + public PlatformUrl getPlatformUrl() { + return this.platformUrl; + } + + private List extractIdentifiers(URL sourceUrl) throws MalformedURLException { + String host = sourceUrl.getPath(); + Pattern pattern = Pattern.compile("/push/v1/organizations/([^/]+)/sources/([^/]+)"); + Matcher matcher = pattern.matcher(host); + + if (matcher.find()) { + String organizationId = matcher.group(1); + String sourceId = matcher.group(2); + return Arrays.asList(organizationId, sourceId); + } + + String errorMessage = this + .getErrorMessage("Unable to find organization and source ids from the provided API url"); + throw new MalformedURLException(errorMessage); + } + + private PlatformUrl extractPlatformUrl(URL sourceUrl) throws MalformedURLException { + String host = sourceUrl.getHost(); + Pattern pattern = Pattern.compile("api([a-z]*)([a-z-]*)\\.cloud\\.coveo\\.com"); + Matcher matcher = pattern.matcher(host); + + if (matcher.find()) { + String extractedEnvironment = matcher.group(1); + String extractedRegion = matcher.group(2).replace("-", ""); + + Environment urlEnvironment = extractedEnvironment.isEmpty() + ? PlatformUrl.DEFAULT_ENVIRONMENT + : EnumSet.allOf(Environment.class) + .stream() + .filter(e -> e.getValue().equalsIgnoreCase(extractedEnvironment)) + .findFirst() + .orElseThrow(() -> new MalformedURLException( + String.format("Invalid platform environment '%s'", extractedEnvironment))); + + Region urlRegion = extractedRegion.isEmpty() + ? PlatformUrl.DEFAULT_REGION + : EnumSet.allOf(Region.class) + .stream() + .filter(r -> r.getValue().equalsIgnoreCase(extractedRegion)) + .findFirst() + .orElseThrow(() -> new MalformedURLException( + String.format("Invalid platform region '%s'", extractedRegion))); + + return new PlatformUrl(urlEnvironment, urlRegion); + + } + + String invalidHostMessage = this.getErrorMessage("Invalid API URL host"); + throw new MalformedURLException(invalidHostMessage); + } + + private String getErrorMessage(String reason) { + String newLine = System.getProperty("line.separator"); + String message = "The provided API URL is invalid"; + + message.concat(newLine).concat(reason); + + message + .concat(newLine) + .concat("For a Push Source, visit: https://docs.coveo.com/en/1546") + .concat(newLine) + .concat("For a Catalog Source, visit:https://docs.coveo.com/en/3295"); + + return message; + } + +} diff --git a/src/main/java/com/coveo/pushapiclient/BaseSource.java b/src/main/java/com/coveo/pushapiclient/BaseSource.java new file mode 100644 index 00000000..a3edd80c --- /dev/null +++ b/src/main/java/com/coveo/pushapiclient/BaseSource.java @@ -0,0 +1,32 @@ +package com.coveo.pushapiclient; + +public interface BaseSource { + /** + * Returns the API key used for all operations regarding your source. + * + * @return + */ + String getApiKey(); + + /** + * Returns the {@link PlatformUrl} object associated to the source. + * + * @return + */ + PlatformUrl getPlatformUrl(); + + /** + * The unique identifier of your organization. + * + * @return + */ + String getOrganizationId(); + + /** + * The unique identifier of your source. + * + * @return + */ + String getId(); + +} diff --git a/src/main/java/com/coveo/pushapiclient/CatalogSource.java b/src/main/java/com/coveo/pushapiclient/CatalogSource.java new file mode 100644 index 00000000..7fc9b08a --- /dev/null +++ b/src/main/java/com/coveo/pushapiclient/CatalogSource.java @@ -0,0 +1,154 @@ +package com.coveo.pushapiclient; + +import java.net.MalformedURLException; +import java.net.URL; + +// TODO: LENS-851 - Make public when ready +class CatalogSource implements StreamEnabledSource { + private final String apiKey; + private final ApiUrl urlExtractor; + + /** + * Create a Catalog source instance from its + * Stream API URL + * + * @param apiKey The API key used for all operations regarding your source. + *

+ * Ensure your API key has the required privileges for the + * operation you will be performing + * * + *

+ * For more information about which privileges are required, + * see + * Privilege + * Reference. + * + * @param sourceUrl The URL available when you edit your source in the Coveo + * Administration Console. The URL should contain your + * ORGANIZATION_ID and SOURCE_ID, + * which are required parameters for all operations regarding + * your source. + *

+ * Some examples of valid source URLs: + * + *

+     * https://api.cloud.coveo.com/push/v1/organizations/my-org-if/sources/my-source-id/stream/open
+     * https://api-eu.cloud.coveo.com/push/v1/organizations/my-org-if/sources/my-source-id/stream/open
+     *                  
+ * + * @throws MalformedURLException + */ + public CatalogSource(String apiKey, URL sourceUrl) throws MalformedURLException { + this.apiKey = apiKey; + this.urlExtractor = new ApiUrl(sourceUrl); + } + + /** + * Create a Catalog source instance from its + * Stream API URL + * + * @param apiKey The API key used for all operations regarding your + * source. + *

+ * Ensure your API key has the required privileges for the + * operation you will be performing + * * + *

+ * For more information about which privileges are + * required, + * see + * Privilege + * Reference. + * + * @param organizationId The unique identifier of your organization. + *

+ * The Organization Id can be retrieved in the URL of your + * Coveo organization. + * + * @param sourceId The unique identifier of the target Catalog source. + *

+ * The Source Id can be retrieved when you edit your + * source in the Coveo + * Administration Console + * + */ + public static CatalogSource fromPlatformUrl(String apiKey, String organizationId, String sourceId) { + PlatformUrl platformUrl = new PlatformUrl(PlatformUrl.DEFAULT_ENVIRONMENT, PlatformUrl.DEFAULT_REGION); + return new CatalogSource(apiKey, organizationId, sourceId, platformUrl); + } + + /** + * Create a Catalog source instance from its + * Stream API URL + * + * @param apiKey The API key used for all operations regarding your + * source. + *

+ * Ensure your API key has the required privileges for the + * operation you will be performing + * * + *

+ * For more information about which privileges are + * required, + * see + * Privilege + * Reference. + * + * @param organizationId The unique identifier of your organization. + *

+ * The Organization Id can be retrieved in the URL of your + * Coveo organization. + * + * @param sourceId The unique identifier of the target Catalog source. + *

+ * The Source Id can be retrieved when you edit your + * source in the Coveo + * Administration Console + * + * @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 static CatalogSource fromPlatformUrl(String apiKey, String organizationId, String sourceId, + PlatformUrl platformUrl) { + return new CatalogSource(apiKey, organizationId, sourceId, platformUrl); + } + + private CatalogSource(String apiKey, String organizationId, String sourceId, PlatformUrl platformUrl) { + this.apiKey = apiKey; + this.urlExtractor = new ApiUrl(organizationId, sourceId, platformUrl); + } + + @Override + public String getOrganizationId() { + return this.urlExtractor.getOrganizationId(); + } + + @Override + public PlatformUrl getPlatformUrl() { + return this.urlExtractor.getPlatformUrl(); + } + + @Override + public String getId() { + return this.urlExtractor.getSourceId(); + } + + @Override + public String getApiKey() { + return this.apiKey; + } + +} diff --git a/src/main/java/com/coveo/pushapiclient/PushEnabledSource.java b/src/main/java/com/coveo/pushapiclient/PushEnabledSource.java new file mode 100644 index 00000000..b90b5f1e --- /dev/null +++ b/src/main/java/com/coveo/pushapiclient/PushEnabledSource.java @@ -0,0 +1,6 @@ +package com.coveo.pushapiclient; + +// Marker Interface +public interface PushEnabledSource extends BaseSource { + +} diff --git a/src/main/java/com/coveo/pushapiclient/PushSource.java b/src/main/java/com/coveo/pushapiclient/PushSource.java new file mode 100644 index 00000000..4a17f9a5 --- /dev/null +++ b/src/main/java/com/coveo/pushapiclient/PushSource.java @@ -0,0 +1,314 @@ +package com.coveo.pushapiclient; + +import com.google.gson.Gson; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.http.HttpResponse; + +// TODO: LENS-851 - Make public when ready +class PushSource implements PushEnabledSource { + private final String apiKey; + private final ApiUrl urlExtractor; + private final PlatformClient platformClient; + + @Override + public String getOrganizationId() { + return this.urlExtractor.getOrganizationId(); + } + + @Override + public PlatformUrl getPlatformUrl() { + return this.urlExtractor.getPlatformUrl(); + } + + @Override + public String getId() { + return this.urlExtractor.getSourceId(); + } + + @Override + public String getApiKey() { + return this.apiKey; + } + + /** + * Create a Push source instance from its + * Push API URL + * + * @param apiKey The API key used for all operations regarding your source. + *

+ * Ensure your API key has the required privileges for the + * operation you will be performing + * * + *

+ * For more information about which privileges are required, + * see + * Privilege + * Reference. + * + * @param sourceUrl The URL available when you edit your source in the Coveo + * Administration Console. The URL should contain your + * ORGANIZATION_ID and SOURCE_ID, + * which are required parameters for all operations regarding + * your source. + *

+ * Some examples of valid source URLs: + * + *

+     * https://api.cloud.coveo.com/push/v1/organizations/my-org-if/sources/my-source-id/documents
+     * https://api-eu.cloud.coveo.com/push/v1/organizations/my-org-if/sources/my-source-id/documents
+     *                  
+ * + * @throws MalformedURLException + */ + public PushSource(String apiKey, URL sourceUrl) throws MalformedURLException { + this.apiKey = apiKey; + this.urlExtractor = new ApiUrl(sourceUrl); + String organizationId = urlExtractor.getOrganizationId(); + PlatformUrl platformUrl = urlExtractor.getPlatformUrl(); + this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl); + } + + /** + * Create a Push source instance + * + * @param apiKey The API key used for all operations regarding your + * source. + *

+ * Ensure your API key has the required privileges for the + * operation you will be performing + * * + *

+ * For more information about which privileges are + * required, + * see + * Privilege + * Reference. + * + * @param organizationId The unique identifier of your organization. + *

+ * The Organization Id can be retrieved in the URL of your + * Coveo organization. + * + * @param sourceId The unique identifier of the target Push source. + *

+ * The Source Id can be retrieved when you edit your + * source in the Coveo + * Administration Console + * + */ + public static PushSource fromPlatformUrl(String apiKey, String organizationId, String sourceId) { + PlatformUrl platformUrl = new PlatformUrl(PlatformUrl.DEFAULT_ENVIRONMENT, PlatformUrl.DEFAULT_REGION); + return new PushSource(apiKey, organizationId, sourceId, platformUrl); + } + + /** + * Create a Push source instance + * + * @param apiKey The API key used for all operations regarding your + * source. + *

+ * Ensure your API key has the required privileges for the + * operation you will be performing + * * + *

+ * For more information about which privileges are + * required, + * see + * Privilege + * Reference. + * + * @param organizationId The unique identifier of your organization. + *

+ * The Organization Id can be retrieved in the URL of your + * Coveo organization. + * + * @param sourceId The unique identifier of the target Push source. + *

+ * The Source Id can be retrieved when you edit your + * source in the Coveo + * Administration Console + * + * @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 static PushSource fromPlatformUrl(String apiKey, String organizationId, String sourceId, + PlatformUrl platformUrl) { + return new PushSource(apiKey, organizationId, sourceId, platformUrl); + } + + 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); + } + + /** + * Create or update a security identity. See [Adding a Single Security + * Identity](https://docs.coveo.com/en/167) and [Security Identity + * Models](https://docs.coveo.com/en/139). + * + * @param securityProviderId + * @param securityIdentityModel + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse createOrUpdateSecurityIdentity(String securityProviderId, + SecurityIdentityModel securityIdentityModel) throws IOException, InterruptedException { + return this.platformClient.createOrUpdateSecurityIdentity(securityProviderId, securityIdentityModel); + } + + /** + * Create or update a security identity alias. See [Adding a Single + * Alias](https://docs.coveo.com/en/142) and [User Alias Definition + * Examples](https://docs.coveo.com/en/46). + * + * @param securityProviderId + * @param securityIdentityAliasModel + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse createOrUpdateSecurityIdentityAlias(String securityProviderId, + SecurityIdentityAliasModel securityIdentityAliasModel) throws IOException, InterruptedException { + return this.platformClient.createOrUpdateSecurityIdentityAlias(securityProviderId, securityIdentityAliasModel); + } + + /** + * Delete a security identity. See [Disabling a Single Security + * Identity](https://docs.coveo.com/en/84). + * + * @param securityProviderId + * @param securityIdentityDelete + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse deleteSecurityIdentity(String securityProviderId, + SecurityIdentityDelete securityIdentityDelete) throws IOException, InterruptedException { + return this.platformClient.deleteSecurityIdentity(securityProviderId, securityIdentityDelete); + } + + /** + * Update the status of a Push source. See [Updating the Status of a Push + * Source](https://docs.coveo.com/en/35). + * + * @param status + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse updateSourceStatus(PushAPIStatus status) + throws IOException, InterruptedException { + return this.platformClient.updateSourceStatus(this.getId(), status); + } + + /** + * Delete old security identities. See [Disabling Old Security + * Identities](https://docs.coveo.com/en/33). + * + * @param securityProviderId + * @param batchDelete + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse deleteOldSecurityIdentities(String securityProviderId, + SecurityIdentityDeleteOptions batchDelete) throws IOException, InterruptedException { + return this.platformClient.deleteOldSecurityIdentities(securityProviderId, batchDelete); + } + + /** + * Manage batches of security identities. See [Manage Batches of Security + * Identities](https://docs.coveo.com/en/55). + * + * @param securityProviderId + * @param batchConfig + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse manageSecurityIdentities(String securityProviderId, + SecurityIdentityBatchConfig batchConfig) throws IOException, InterruptedException { + return this.platformClient.manageSecurityIdentities(securityProviderId, batchConfig); + } + + /** + * Manages pushing batches of Security Identities to a File Container, then into + * Coveo. See [Manage Batches of Security + * Identities](https://docs.coveo.com/en/55) + * + * @param securityProviderId + * @param batchIdentity + * @return + * @throws IOException + * @throws InterruptedException + */ + public SecurityIdentityBatchResponse batchUpdateSecurityIdentities(String securityProviderId, + BatchIdentity batchIdentity) throws IOException, InterruptedException { + SecurityIdentityBatchResponse securityIdentityBatchResponse = new SecurityIdentityBatchResponse(); + HttpResponse resFileContainer = this.platformClient.createFileContainer(); + FileContainer fileContainer = new Gson().fromJson(resFileContainer.body(), FileContainer.class); + String batchIdJson = new Gson().toJson(batchIdentity.marshal()); + securityIdentityBatchResponse.s3Response = this.platformClient.uploadContentToFileContainer(fileContainer, + batchIdJson); + if (securityIdentityBatchResponse.s3Response.statusCode() >= 200 + && securityIdentityBatchResponse.s3Response.statusCode() <= 299) { // maybe just 200 or 202 + SecurityIdentityBatchConfig batchConfig = new SecurityIdentityBatchConfig(fileContainer.fileId, 0l); + securityIdentityBatchResponse.batchResponse = this.manageSecurityIdentities(securityProviderId, + batchConfig); + } + return securityIdentityBatchResponse; + } + + /** + * Adds or updates an individual item in a push source. See [Adding a Single + * Item in a Push Source](https://docs.coveo.com/en/133). + * + * @param docBuilder + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse addOrUpdateDocument(DocumentBuilder docBuilder) + throws IOException, InterruptedException { + CompressionType compressionType = docBuilder.getDocument().compressedBinaryData != null + ? docBuilder.getDocument().compressedBinaryData.getCompressionType() + : CompressionType.UNCOMPRESSED; + return this.platformClient.pushDocument(this.getId(), docBuilder.marshal(), docBuilder.getDocument().uri, + compressionType); + } + + /** + * Deletes a specific item from a Push source. Optionally, the child items of + * that item can also be deleted. See [Deleting an Item in a Push + * Source](https://docs.coveo.com/en/171). + * + * @param documentId + * @param deleteChildren + * @return + * @throws IOException + * @throws InterruptedException + */ + public HttpResponse deleteDocument(String documentId, Boolean deleteChildren) + throws IOException, InterruptedException { + return this.platformClient.deleteDocument(this.getId(), documentId, deleteChildren); + } + +} diff --git a/src/main/java/com/coveo/pushapiclient/Source.java b/src/main/java/com/coveo/pushapiclient/Source.java index 3b09a562..2288d944 100644 --- a/src/main/java/com/coveo/pushapiclient/Source.java +++ b/src/main/java/com/coveo/pushapiclient/Source.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.net.http.HttpResponse; +// TODO: LENS-844 - Deprecate class public class Source { PlatformClient platformClient; diff --git a/src/main/java/com/coveo/pushapiclient/StreamEnabledSource.java b/src/main/java/com/coveo/pushapiclient/StreamEnabledSource.java new file mode 100644 index 00000000..b04912bf --- /dev/null +++ b/src/main/java/com/coveo/pushapiclient/StreamEnabledSource.java @@ -0,0 +1,6 @@ +package com.coveo.pushapiclient; + +// Marker Interface +public interface StreamEnabledSource extends BaseSource { + +} diff --git a/src/test/java/com/coveo/pushapiclient/ApiUrlTest.java b/src/test/java/com/coveo/pushapiclient/ApiUrlTest.java new file mode 100644 index 00000000..964dcb55 --- /dev/null +++ b/src/test/java/com/coveo/pushapiclient/ApiUrlTest.java @@ -0,0 +1,87 @@ +package com.coveo.pushapiclient; + +import static org.junit.Assert.assertEquals; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.junit.Test; + +public class ApiUrlTest { + + @Test + public void testSourceId() throws MalformedURLException { + ApiUrl url = new ApiUrl( + new URL("https://api.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + assertEquals(url.getSourceId(), "my-source-id"); + } + + @Test + public void testOrganizationId() throws MalformedURLException { + ApiUrl url = new ApiUrl( + new URL("https://api.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + assertEquals(url.getOrganizationId(), "my-org-id"); + } + + @Test + public void testPlatformUrl() throws MalformedURLException { + ApiUrl defaultUrl = new ApiUrl( + new URL("https://api.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + ApiUrl regionOnlyUrl = new ApiUrl( + new URL("https://api-au.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + ApiUrl environmentOnlyUrl = new ApiUrl( + new URL("https://apidev.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + ApiUrl environmentAndRegionUrl = new ApiUrl( + new URL("https://apidev-au.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + + assertEquals(defaultUrl.getPlatformUrl().getApiUrl(), "https://api.cloud.coveo.com"); + assertEquals(regionOnlyUrl.getPlatformUrl().getApiUrl(), "https://api-au.cloud.coveo.com"); + assertEquals(environmentOnlyUrl.getPlatformUrl().getApiUrl(), "https://apidev.cloud.coveo.com"); + assertEquals(environmentAndRegionUrl.getPlatformUrl().getApiUrl(), "https://apidev-au.cloud.coveo.com"); + } + + @Test + public void testStreamApiUrl() throws MalformedURLException { + ApiUrl url = new ApiUrl( + new URL("https://apidev-au.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/stream/open")); + + assertEquals(url.getPlatformUrl().getApiUrl(), "https://apidev-au.cloud.coveo.com"); + assertEquals(url.getOrganizationId(), "my-org-id"); + assertEquals(url.getSourceId(), "my-source-id"); + } + + @Test(expected = MalformedURLException.class) + public void testInvalidEnvironmentUrl() throws MalformedURLException { + new ApiUrl( + new URL("https://apifoo.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + + } + @Test(expected = MalformedURLException.class) + public void testInvalidUrl() throws MalformedURLException { + new ApiUrl( + new URL("https://apifoo.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + + } + + @Test(expected = MalformedURLException.class) + public void testInvalidRegionUrl() throws MalformedURLException { + new ApiUrl( + new URL( + "https://api-bar.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + + } + + @Test(expected = MalformedURLException.class) + public void testInvalidPathUrl() throws MalformedURLException { + new ApiUrl( + new URL("https://api.cloud.coveo.com/push/v1/organizations/my-org-id/providers/provider-id/mappings")); + + } + + @Test(expected = MalformedURLException.class) + public void testInvalidHostUrl() throws MalformedURLException { + new ApiUrl( + new URL("https://platform.cloud.coveo.com/push/v1/organizations/my-org-id/sources/my-source-id/documents")); + + } +}