From 25b614783d6ceb98b1258674bcb04819a5a6029f Mon Sep 17 00:00:00 2001 From: Rajesh Nagpal Date: Mon, 5 Oct 2015 19:55:58 -0700 Subject: [PATCH] Java SDK 1.4.0 release Upsert and ID based routing implementation --- .classpath | 2 +- README.md | 5 +- changelog.md | 11 +- pom.xml | 9 +- .../azure/documentdb/AuthorizationHelper.java | 33 +- .../azure/documentdb/DocumentClient.java | 389 ++- .../documentdb/DocumentServiceRequest.java | 14 +- .../azure/documentdb/GatewayProxy.java | 32 +- .../microsoft/azure/documentdb/HashIndex.java | 40 +- .../azure/documentdb/HttpConstants.java | 14 +- .../azure/documentdb/IncludedPath.java | 44 +- src/com/microsoft/azure/documentdb/Index.java | 89 + .../azure/documentdb/IndexingPolicy.java | 46 +- .../azure/documentdb/RangeIndex.java | 36 +- .../azure/documentdb/ResourceId.java | 2 +- .../azure/documentdb/ResourceResponse.java | 13 + .../azure/documentdb/SessionContainer.java | 112 +- .../azure/documentdb/SpatialIndex.java | 18 +- src/com/microsoft/azure/documentdb/Utils.java | 129 + .../azure/documentdb/test/GatewayTests.java | 2147 +++++++++++++---- 20 files changed, 2544 insertions(+), 641 deletions(-) create mode 100644 src/com/microsoft/azure/documentdb/Utils.java diff --git a/.classpath b/.classpath index 6565430..1e3e7e2 100644 --- a/.classpath +++ b/.classpath @@ -17,4 +17,4 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index b8bba23..ad7498e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ To get the binaries of this library as distributed by Microsoft, ready for use w com.microsoft.azure azure-documentdb - 1.2.0 + 1.4.0 ###Option 2: Source Via Git @@ -35,6 +35,7 @@ To download a copy of the source code, click "Download ZIP" on the right side of ### Dependencies * Apache Commons Lang 3.3.2 (org.apache.commons / commons-lang3 / 3.3.2) * Apache HttpClient 4.2.5 (org.apache.httpcomponents / httpclient / 4.2.5) +* Apache HttpCore 4.2.5 (org.apache.httpcomponents / httpcore / 4.2.5) * Jackson Data Mapper 1.8 (org.codehaus.jackson / jackson-mapper-asl / 1.8.5) * JSON 20140107 (org.json / json / 20140107) * JUnit 4.11 (junit / junit / 4.11) @@ -91,7 +92,7 @@ public class SampleApp { myCollection.setId(COLLECTION_ID); // Configure the new collection performance tier to S1. - RequestOptions requestOptions = new RequestOptions(); + RequestOptions requestOptions = new RequestOptions(); requestOptions.setOfferType("S1"); // Create a new collection. diff --git a/changelog.md b/changelog.md index 28715a8..12d99cb 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,16 @@ +## Changes in 1.4.0 : ## + +- Implement Upsert. New upsertXXX methods added to support Upsert feature. +- Implement ID Based Routing. No public API changes, all changes internal. + +## Changes in 1.3.0 : ## + +- Release skipped to bring version number in alignment with other SDKs + ## Changes in 1.2.0 : ## - Supports GeoSpatial index. -- Validates resource id. +- Validates id property for all resources. Ids for resources cannot contain ?, /, #, \\, characters or end with a space. - Adds new header "index transformation progress" to ResourceResponse. ## Changes in 1.1.0 : ## diff --git a/pom.xml b/pom.xml index c907b7b..f64deb4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.microsoft.azure azure-documentdb - 1.2.0 + 1.4.0 ${project.groupId}:${project.artifactId} Java SDK for Microsoft Azure DocumentDB http://azure.microsoft.com/en-us/services/documentdb/ @@ -13,10 +13,13 @@ http://www.opensource.org/licenses/mit-license.php + + UTF-8 + - Pu Shi - pushi@microsoft.com + Rajesh Nagpal + rnagpal@microsoft.com Microsoft http://www.microsoft.com/ diff --git a/src/com/microsoft/azure/documentdb/AuthorizationHelper.java b/src/com/microsoft/azure/documentdb/AuthorizationHelper.java index 210b035..5786138 100644 --- a/src/com/microsoft/azure/documentdb/AuthorizationHelper.java +++ b/src/com/microsoft/azure/documentdb/AuthorizationHelper.java @@ -27,14 +27,14 @@ final class AuthorizationHelper { * This API is a helper method to create auth header based on client request using masterkey. * * @param verb the verb. - * @param resourceId the resource id. + * @param resourceIdOrFullName the resource id or full name * @param resourceType the resource type. * @param headers the request headers. * @param masterKey the master key. * @return the key authorization signature. */ public static String GenerateKeyAuthorizationSignature(String verb, - String resourceId, + String resourceIdOrFullName, ResourceType resourceType, Map headers, String masterKey) { @@ -42,8 +42,8 @@ public static String GenerateKeyAuthorizationSignature(String verb, throw new IllegalArgumentException("verb"); } - if (resourceId == null) { - resourceId = ""; + if (resourceIdOrFullName == null) { + resourceIdOrFullName = ""; } if (resourceType == null) { @@ -60,26 +60,25 @@ public static String GenerateKeyAuthorizationSignature(String verb, byte[] decodedBytes = Base64.decodeBase64(masterKey.getBytes()); SecretKey signingKey = new SecretKeySpec(decodedBytes, "HMACSHA256"); - - String text = String.format("%s\n%s\n%s\n", - verb, - AuthorizationHelper.getResourceSegement(resourceType), - resourceId.toLowerCase()); + + // Skipping lower casing of resourceId since it may now contain "ID" of the resource as part of the FullName + String body = String.format("%s\n%s\n%s\n", + verb.toLowerCase(), + AuthorizationHelper.getResourceSegement(resourceType).toLowerCase(), + resourceIdOrFullName); if (headers.containsKey(HttpConstants.HttpHeaders.X_DATE)) { - text += headers.get(HttpConstants.HttpHeaders.X_DATE); + body += headers.get(HttpConstants.HttpHeaders.X_DATE).toLowerCase(); } - text += '\n'; + body += '\n'; if (headers.containsKey(HttpConstants.HttpHeaders.HTTP_DATE)) { - text += headers.get(HttpConstants.HttpHeaders.HTTP_DATE); + body += headers.get(HttpConstants.HttpHeaders.HTTP_DATE).toLowerCase(); } - text += '\n'; - - String body = text.toLowerCase(); - + body += '\n'; + Mac mac = null; try { mac = Mac.getInstance("HMACSHA256"); @@ -96,7 +95,7 @@ public static String GenerateKeyAuthorizationSignature(String verb, byte[] digest = mac.doFinal(body.getBytes()); - String auth = Helper.encodeBase64String(digest); + String auth = Utils.encodeBase64String(digest); String authtoken = "type=master&ver=1.0&sig=" + auth; diff --git a/src/com/microsoft/azure/documentdb/DocumentClient.java b/src/com/microsoft/azure/documentdb/DocumentClient.java index 940cba3..df6231d 100644 --- a/src/com/microsoft/azure/documentdb/DocumentClient.java +++ b/src/com/microsoft/azure/documentdb/DocumentClient.java @@ -185,7 +185,7 @@ public ResourceResponse deleteDatabase(String databaseLink, RequestOpt throw new IllegalArgumentException("databaseLink"); } - String path = DocumentClient.joinPath(databaseLink, null); + String path = Utils.joinPath(databaseLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Database, path, @@ -207,7 +207,7 @@ public ResourceResponse readDatabase(String databaseLink, RequestOptio throw new IllegalArgumentException("databaseLink"); } - String path = DocumentClient.joinPath(databaseLink, null); + String path = Utils.joinPath(databaseLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Database, path, @@ -288,7 +288,7 @@ public ResourceResponse createCollection(String databaseLink DocumentClient.validateResource(collection); - String path = DocumentClient.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); + String path = Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.DocumentCollection, path, @@ -315,7 +315,7 @@ public ResourceResponse replaceCollection(DocumentCollection DocumentClient.validateResource(collection); - String path = DocumentClient.joinPath(collection.getSelfLink(), null); + String path = Utils.joinPath(collection.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.DocumentCollection, @@ -340,7 +340,7 @@ public ResourceResponse deleteCollection(String collectionLi throw new IllegalArgumentException("collectionLink"); } - String path = DocumentClient.joinPath(collectionLink, null); + String path = Utils.joinPath(collectionLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.DocumentCollection, path, @@ -363,7 +363,7 @@ public ResourceResponse readCollection(String collectionLink throw new IllegalArgumentException("collectionLink"); } - String path = DocumentClient.joinPath(collectionLink, null); + String path = Utils.joinPath(collectionLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.DocumentCollection, path, @@ -384,7 +384,7 @@ public FeedResponse readCollections(String databaseLink, Fee throw new IllegalArgumentException("databaseLink"); } - String path = DocumentClient.joinPath(databaseLink, + String path = Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.DocumentCollection, @@ -437,7 +437,7 @@ public FeedResponse queryCollections(String databaseLink, throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); + String path = Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.DocumentCollection, path, @@ -465,6 +465,33 @@ public ResourceResponse createDocument(String collectionLink, RequestOptions options, boolean disableAutomaticIdGeneration) throws DocumentClientException { + DocumentServiceRequest request = getDocumentRequest(collectionLink, document, options, + disableAutomaticIdGeneration); + return new ResourceResponse(this.doCreate(request), Document.class); + } + + /** + * Upserts a document. + * + * @param collectionLink the collection link. + * @param document the document represented as a POJO or Document object to upsert. + * @param options the request options. + * @param disableAutomaticIdGeneration the flag for disabling automatic id generation. + * @return the resource response with the upserted document. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertDocument(String collectionLink, + Object document, + RequestOptions options, + boolean disableAutomaticIdGeneration) + throws DocumentClientException { + DocumentServiceRequest request = getDocumentRequest(collectionLink, document, options, + disableAutomaticIdGeneration); + return new ResourceResponse(this.doUpsert(request), Document.class); + } + + private DocumentServiceRequest getDocumentRequest(String collectionLink, Object document, RequestOptions options, + boolean disableAutomaticIdGeneration) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } @@ -481,15 +508,15 @@ public ResourceResponse createDocument(String collectionLink, // when represented as a string. typedDocument.setId(UUID.randomUUID().toString()); } - String path = DocumentClient.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Document, path, typedDocument, requestHeaders); - return new ResourceResponse(this.doCreate(request), Document.class); + return request; } - + /** * Replaces a document using a POJO object. * @@ -512,7 +539,7 @@ public ResourceResponse replaceDocument(String documentLink, Object do Document typedDocument = Document.FromObject(document); DocumentClient.validateResource(typedDocument); - String path = DocumentClient.joinPath(documentLink, null); + String path = Utils.joinPath(documentLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Document, @@ -539,7 +566,7 @@ public ResourceResponse replaceDocument(Document document, RequestOpti DocumentClient.validateResource(document); - String path = DocumentClient.joinPath(document.getSelfLink(), null); + String path = Utils.joinPath(document.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Document, path, @@ -563,7 +590,7 @@ public ResourceResponse deleteDocument(String documentLink, RequestOpt throw new IllegalArgumentException("documentLink"); } - String path = DocumentClient.joinPath(documentLink, null); + String path = Utils.joinPath(documentLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Document, path, requestHeaders); return new ResourceResponse(this.doDelete(request), Document.class); @@ -584,7 +611,7 @@ public ResourceResponse readDocument(String documentLink, RequestOptio throw new IllegalArgumentException("documentLink"); } - String path = DocumentClient.joinPath(documentLink, null); + String path = Utils.joinPath(documentLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Document, path, requestHeaders); return new ResourceResponse(this.doRead(request), Document.class); @@ -603,7 +630,7 @@ public FeedResponse readDocuments(String collectionLink, FeedOptions o throw new IllegalArgumentException("collectionLink"); } - String path = DocumentClient.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Document, path, requestHeaders); return new FeedResponse(new QueryIterable(this, request, ReadType.Feed, Document.class)); @@ -646,7 +673,7 @@ public FeedResponse queryDocuments(String collectionLink, SqlQuerySpec throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Document, path, @@ -673,6 +700,30 @@ public ResourceResponse createStoredProcedure(String collection RequestOptions options) throws DocumentClientException { + DocumentServiceRequest request = getStoredProcedureRequest(collectionLink, storedProcedure, options); + return new ResourceResponse(this.doCreate(request), StoredProcedure.class); + } + + /** + * Upserts a stored procedure. + * + * @param collectionLink the collection link. + * @param storedProcedure the stored procedure to upsert. + * @param options the request options. + * @return the resource response with the upserted stored procedure. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertStoredProcedure(String collectionLink, + StoredProcedure storedProcedure, + RequestOptions options) + throws DocumentClientException { + + DocumentServiceRequest request = getStoredProcedureRequest(collectionLink, storedProcedure, options); + return new ResourceResponse(this.doUpsert(request), StoredProcedure.class); + } + + private DocumentServiceRequest getStoredProcedureRequest(String collectionLink, StoredProcedure storedProcedure, + RequestOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } @@ -682,13 +733,13 @@ public ResourceResponse createStoredProcedure(String collection DocumentClient.validateResource(storedProcedure); - String path = DocumentClient.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.StoredProcedure, path, storedProcedure, requestHeaders); - return new ResourceResponse(this.doCreate(request), StoredProcedure.class); + return request; } /** @@ -709,7 +760,7 @@ public ResourceResponse replaceStoredProcedure(StoredProcedure DocumentClient.validateResource(storedProcedure); - String path = DocumentClient.joinPath(storedProcedure.getSelfLink(), null); + String path = Utils.joinPath(storedProcedure.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.StoredProcedure, path, @@ -733,7 +784,7 @@ public ResourceResponse deleteStoredProcedure(String storedProc throw new IllegalArgumentException("storedProcedureLink"); } - String path = DocumentClient.joinPath(storedProcedureLink, null); + String path = Utils.joinPath(storedProcedureLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.StoredProcedure, path, @@ -756,7 +807,7 @@ public ResourceResponse readStoredProcedure(String storedProced throw new IllegalArgumentException("storedProcedureLink"); } - String path = DocumentClient.joinPath(storedProcedureLink, null); + String path = Utils.joinPath(storedProcedureLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.StoredProcedure, path, @@ -777,7 +828,7 @@ public FeedResponse readStoredProcedures(String collectionLink, throw new IllegalArgumentException("collectionLink"); } - String path = DocumentClient.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.StoredProcedure, path, @@ -829,7 +880,7 @@ public FeedResponse queryStoredProcedures(String collectionLink throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.StoredProcedure, path, @@ -852,7 +903,7 @@ public FeedResponse queryStoredProcedures(String collectionLink */ public StoredProcedureResponse executeStoredProcedure(String storedProcedureLink, Object[] procedureParams) throws DocumentClientException { - String path = DocumentClient.joinPath(storedProcedureLink, null); + String path = Utils.joinPath(storedProcedureLink, null); Map requestHeaders = new HashMap(); requestHeaders.put(HttpConstants.HttpHeaders.ACCEPT, RuntimeConstants.MediaTypes.JSON); DocumentServiceRequest request = DocumentServiceRequest.create( @@ -875,6 +926,27 @@ public StoredProcedureResponse executeStoredProcedure(String storedProcedureLink public ResourceResponse createTrigger(String collectionLink, Trigger trigger, RequestOptions options) throws DocumentClientException { + DocumentServiceRequest request = getTriggerRequest(collectionLink, trigger, options); + return new ResourceResponse(this.doCreate(request), Trigger.class); + } + + /** + * Upserts a trigger. + * + * @param collectionLink the collection link. + * @param trigger the trigger to upsert. + * @param options the request options. + * @return the resource response with the upserted trigger. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertTrigger(String collectionLink, Trigger trigger, RequestOptions options) + throws DocumentClientException { + + DocumentServiceRequest request = getTriggerRequest(collectionLink, trigger, options); + return new ResourceResponse(this.doUpsert(request), Trigger.class); + } + + private DocumentServiceRequest getTriggerRequest(String collectionLink, Trigger trigger, RequestOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } @@ -884,13 +956,13 @@ public ResourceResponse createTrigger(String collectionLink, Trigger tr DocumentClient.validateResource(trigger); - String path = DocumentClient.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Trigger, path, trigger, requestHeaders); - return new ResourceResponse(this.doCreate(request), Trigger.class); + return request; } /** @@ -910,7 +982,7 @@ public ResourceResponse replaceTrigger(Trigger trigger, RequestOptions DocumentClient.validateResource(trigger); - String path = DocumentClient.joinPath(trigger.getSelfLink(), null); + String path = Utils.joinPath(trigger.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Trigger, path, @@ -934,7 +1006,7 @@ public ResourceResponse deleteTrigger(String triggerLink, RequestOption throw new IllegalArgumentException("triggerLink"); } - String path = DocumentClient.joinPath(triggerLink, null); + String path = Utils.joinPath(triggerLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Trigger, path, requestHeaders); return new ResourceResponse(this.doDelete(request), Trigger.class); @@ -955,7 +1027,7 @@ public ResourceResponse readTrigger(String triggerLink, RequestOptions throw new IllegalArgumentException("triggerLink"); } - String path = DocumentClient.joinPath(triggerLink, null); + String path = Utils.joinPath(triggerLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Trigger, path, requestHeaders); return new ResourceResponse(this.gatewayProxy.doRead(request), Trigger.class); @@ -974,7 +1046,7 @@ public FeedResponse readTriggers(String collectionLink, FeedOptions opt throw new IllegalArgumentException("collectionLink"); } - String path = DocumentClient.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Trigger, path, requestHeaders); return new FeedResponse(new QueryIterable(this, request, ReadType.Feed, Trigger.class)); @@ -1023,7 +1095,7 @@ public FeedResponse queryTriggers(String collectionLink, throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Trigger, path, @@ -1048,6 +1120,31 @@ public ResourceResponse createUserDefinedFunction( RequestOptions options) throws DocumentClientException { + DocumentServiceRequest request = getUserDefinedFunctionRequest(collectionLink, udf, options); + return new ResourceResponse(this.doCreate(request), UserDefinedFunction.class); + } + + /** + * Upserts a user defined function. + * + * @param collectionLink the collection link. + * @param udf the user defined function to upsert. + * @param options the request options. + * @return the resource response with the upserted user defined function. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertUserDefinedFunction( + String collectionLink, + UserDefinedFunction udf, + RequestOptions options) + throws DocumentClientException { + + DocumentServiceRequest request = getUserDefinedFunctionRequest(collectionLink, udf, options); + return new ResourceResponse(this.doUpsert(request), UserDefinedFunction.class); + } + + private DocumentServiceRequest getUserDefinedFunctionRequest(String collectionLink, UserDefinedFunction udf, + RequestOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } @@ -1057,13 +1154,13 @@ public ResourceResponse createUserDefinedFunction( DocumentClient.validateResource(udf); - String path = DocumentClient.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.UserDefinedFunction, path, udf, requestHeaders); - return new ResourceResponse(this.doCreate(request), UserDefinedFunction.class); + return request; } /** @@ -1083,7 +1180,7 @@ public ResourceResponse replaceUserDefinedFunction(UserDefi DocumentClient.validateResource(udf); - String path = DocumentClient.joinPath(udf.getSelfLink(), null); + String path = Utils.joinPath(udf.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.UserDefinedFunction, path, @@ -1106,7 +1203,7 @@ public ResourceResponse deleteUserDefinedFunction(String ud throw new IllegalArgumentException("udfLink"); } - String path = DocumentClient.joinPath(udfLink, null); + String path = Utils.joinPath(udfLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.UserDefinedFunction, path, @@ -1128,7 +1225,7 @@ public ResourceResponse readUserDefinedFunction(String udfL throw new IllegalArgumentException("udfLink"); } - String path = DocumentClient.joinPath(udfLink, null); + String path = Utils.joinPath(udfLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.UserDefinedFunction, path, @@ -1149,7 +1246,7 @@ public FeedResponse readUserDefinedFunctions(String collect throw new IllegalArgumentException("collectionLink"); } - String path = DocumentClient.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.UserDefinedFunction, path, @@ -1203,7 +1300,7 @@ public FeedResponse queryUserDefinedFunctions(String collec throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.UserDefinedFunction, path, @@ -1229,6 +1326,29 @@ public ResourceResponse createAttachment(String documentLink, Attachment attachment, RequestOptions options) throws DocumentClientException { + DocumentServiceRequest request = getAttachmentRequest(documentLink, attachment, options); + return new ResourceResponse(this.doCreate(request), Attachment.class); + } + + /** + * Upserts an attachment. + * + * @param documentLink the document link. + * @param attachment the attachment to upsert. + * @param options the request options. + * @return the resource response with the upserted attachment. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertAttachment(String documentLink, + Attachment attachment, + RequestOptions options) + throws DocumentClientException { + DocumentServiceRequest request = getAttachmentRequest(documentLink, attachment, options); + return new ResourceResponse(this.doUpsert(request), Attachment.class); + } + + private DocumentServiceRequest getAttachmentRequest(String documentLink, Attachment attachment, + RequestOptions options) { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } @@ -1238,15 +1358,15 @@ public ResourceResponse createAttachment(String documentLink, DocumentClient.validateResource(attachment); - String path = DocumentClient.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); + String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Attachment, path, attachment, requestHeaders); - return new ResourceResponse(this.doCreate(request), Attachment.class); + return request; } - + /** * Replaces an attachment. * @@ -1263,7 +1383,7 @@ public ResourceResponse replaceAttachment(Attachment attachment, Req DocumentClient.validateResource(attachment); - String path = DocumentClient.joinPath(attachment.getSelfLink(), null); + String path = Utils.joinPath(attachment.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Attachment, path, @@ -1286,7 +1406,7 @@ public ResourceResponse deleteAttachment(String attachmentLink, Requ throw new IllegalArgumentException("attachmentLink"); } - String path = DocumentClient.joinPath(attachmentLink, null); + String path = Utils.joinPath(attachmentLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Attachment, path, @@ -1308,7 +1428,7 @@ public ResourceResponse readAttachment(String attachmentLink, Reques throw new IllegalArgumentException("attachmentLink"); } - String path = DocumentClient.joinPath(attachmentLink, null); + String path = Utils.joinPath(attachmentLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Attachment, path, requestHeaders); return new ResourceResponse(this.doRead(request), Attachment.class); @@ -1326,7 +1446,7 @@ public FeedResponse readAttachments(String documentLink, FeedOptions throw new IllegalArgumentException("documentLink"); } - String path = DocumentClient.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); + String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Attachment, path, @@ -1374,7 +1494,7 @@ public FeedResponse queryAttachments(String documentLink, SqlQuerySp throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); + String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Attachment, path, @@ -1401,6 +1521,30 @@ public ResourceResponse createAttachment(String documentLink, MediaOptions options) throws DocumentClientException { + DocumentServiceRequest request = getAttachmentRequest(documentLink, mediaStream, options); + return new ResourceResponse(this.doCreate(request), Attachment.class); + } + + /** + * Upserts an attachment to the media stream + * + * @param documentLink the document link. + * @param mediaStream the media stream for upserting the attachment. + * @param options the media options. + * @return the resource response with the upserted attachment. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertAttachment(String documentLink, + InputStream mediaStream, + MediaOptions options) + throws DocumentClientException { + + DocumentServiceRequest request = getAttachmentRequest(documentLink, mediaStream, options); + return new ResourceResponse(this.doUpsert(request), Attachment.class); + } + + private DocumentServiceRequest getAttachmentRequest(String documentLink, InputStream mediaStream, + MediaOptions options) { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } @@ -1408,14 +1552,14 @@ public ResourceResponse createAttachment(String documentLink, throw new IllegalArgumentException("mediaStream"); } - String path = DocumentClient.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); + String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); Map requestHeaders = this.getMediaHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Attachment, path, mediaStream, requestHeaders); request.setIsMedia(true); - return new ResourceResponse(this.doCreate(request), Attachment.class); + return request; } /** @@ -1431,7 +1575,7 @@ public MediaResponse readMedia(String mediaLink) throws DocumentClientException throw new IllegalArgumentException("mediaLink"); } - String path = DocumentClient.joinPath(mediaLink, null); + String path = Utils.joinPath(mediaLink, null); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Media, path, null); request.setIsMedia(true); return new MediaResponse(this.doRead(request), @@ -1457,7 +1601,7 @@ public MediaResponse updateMedia(String mediaLink, InputStream mediaStream, Medi throw new IllegalArgumentException("mediaStream"); } - String path = DocumentClient.joinPath(mediaLink, null); + String path = Utils.joinPath(mediaLink, null); Map requestHeaders = this.getMediaHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Media, path, @@ -1482,7 +1626,7 @@ public ResourceResponse readConflict(String conflictLink, RequestOptio throw new IllegalArgumentException("conflictLink"); } - String path = DocumentClient.joinPath(conflictLink, null); + String path = Utils.joinPath(conflictLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Conflict, path, requestHeaders); return new ResourceResponse(this.doRead(request), Conflict.class); @@ -1502,7 +1646,7 @@ public FeedResponse readConflicts( throw new IllegalArgumentException("collectionLink"); } - String path = DocumentClient.joinPath(collectionLink, + String path = Utils.joinPath(collectionLink, Paths.CONFLICTS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create( @@ -1551,7 +1695,7 @@ public FeedResponse queryConflicts(String collectionLink, SqlQuerySpec throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(collectionLink, Paths.CONFLICTS_PATH_SEGMENT); + String path = Utils.joinPath(collectionLink, Paths.CONFLICTS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Conflict, path, @@ -1576,7 +1720,7 @@ public ResourceResponse deleteConflict(String conflictLink, RequestOpt throw new IllegalArgumentException("conflictLink"); } - String path = DocumentClient.joinPath(conflictLink, null); + String path = Utils.joinPath(conflictLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Conflict, path, requestHeaders); return new ResourceResponse(this.doDelete(request), Conflict.class); @@ -1594,6 +1738,27 @@ public ResourceResponse deleteConflict(String conflictLink, RequestOpt public ResourceResponse createUser(String databaseLink, User user, RequestOptions options) throws DocumentClientException { + DocumentServiceRequest request = getUserRequest(databaseLink, user, options); + return new ResourceResponse(this.doCreate(request), User.class); + } + + /** + * Upserts a user. + * + * @param databaseLink the database link. + * @param user the user to upsert. + * @param options the request options. + * @return the resource response with the upserted user. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertUser(String databaseLink, User user, RequestOptions options) + throws DocumentClientException { + + DocumentServiceRequest request = getUserRequest(databaseLink, user, options); + return new ResourceResponse(this.doUpsert(request), User.class); + } + + private DocumentServiceRequest getUserRequest(String databaseLink, User user, RequestOptions options) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } @@ -1603,10 +1768,10 @@ public ResourceResponse createUser(String databaseLink, User user, Request DocumentClient.validateResource(user); - String path = DocumentClient.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); + String path = Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.User, path, user, requestHeaders); - return new ResourceResponse(this.doCreate(request), User.class); + return request; } /** @@ -1625,7 +1790,7 @@ public ResourceResponse replaceUser(User user, RequestOptions options) thr DocumentClient.validateResource(user); - String path = DocumentClient.joinPath(user.getSelfLink(), null); + String path = Utils.joinPath(user.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.User, path, user, requestHeaders); return new ResourceResponse(this.doReplace(request), User.class); @@ -1645,7 +1810,7 @@ public ResourceResponse deleteUser(String userLink, RequestOptions options throw new IllegalArgumentException("userLink"); } - String path = DocumentClient.joinPath(userLink, null); + String path = Utils.joinPath(userLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.User, path, requestHeaders); return new ResourceResponse(this.doDelete(request), User.class); @@ -1665,7 +1830,7 @@ public ResourceResponse readUser(String userLink, RequestOptions options) throw new IllegalArgumentException("userLink"); } - String path = DocumentClient.joinPath(userLink, null); + String path = Utils.joinPath(userLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.User, path, requestHeaders); return new ResourceResponse(this.doRead(request), User.class); @@ -1684,7 +1849,7 @@ public FeedResponse readUsers(String databaseLink, FeedOptions options) { throw new IllegalArgumentException("databaseLink"); } - String path = DocumentClient.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); + String path = Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.User, path, requestHeaders); return new FeedResponse(new QueryIterable(this, request, ReadType.Feed, User.class)); @@ -1729,7 +1894,7 @@ public FeedResponse queryUsers(String databaseLink, SqlQuerySpec querySpec throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); + String path = Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.User, path, @@ -1751,6 +1916,28 @@ public FeedResponse queryUsers(String databaseLink, SqlQuerySpec querySpec public ResourceResponse createPermission(String userLink, Permission permission, RequestOptions options) throws DocumentClientException { + DocumentServiceRequest request = getPermissionRequest(userLink, permission, options); + return new ResourceResponse(this.doCreate(request), Permission.class); + } + + /** + * Upserts a permission. + * + * @param userLink the user link. + * @param permission the permission to upsert. + * @param options the request options. + * @return the resource response with the upserted permission. + * @throws DocumentClientException the document client exception. + */ + public ResourceResponse upsertPermission(String userLink, Permission permission, RequestOptions options) + throws DocumentClientException { + + DocumentServiceRequest request = getPermissionRequest(userLink, permission, options); + return new ResourceResponse(this.doUpsert(request), Permission.class); + } + + private DocumentServiceRequest getPermissionRequest(String userLink, Permission permission, + RequestOptions options) { if (StringUtils.isEmpty(userLink)) { throw new IllegalArgumentException("userLink"); } @@ -1760,13 +1947,13 @@ public ResourceResponse createPermission(String userLink, Permission DocumentClient.validateResource(permission); - String path = DocumentClient.joinPath(userLink, Paths.PERMISSIONS_PATH_SEGMENT); + String path = Utils.joinPath(userLink, Paths.PERMISSIONS_PATH_SEGMENT); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Permission, path, permission, requestHeaders); - return new ResourceResponse(this.doCreate(request), Permission.class); + return request; } /** @@ -1786,7 +1973,7 @@ public ResourceResponse replacePermission(Permission permission, Req DocumentClient.validateResource(permission); - String path = DocumentClient.joinPath(permission.getSelfLink(), null); + String path = Utils.joinPath(permission.getSelfLink(), null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Permission, path, @@ -1810,7 +1997,7 @@ public ResourceResponse deletePermission(String permissionLink, Requ throw new IllegalArgumentException("permissionLink"); } - String path = DocumentClient.joinPath(permissionLink, null); + String path = Utils.joinPath(permissionLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Permission, path, requestHeaders); return new ResourceResponse(this.doDelete(request), Permission.class); @@ -1831,7 +2018,7 @@ public ResourceResponse readPermission(String permissionLink, Reques throw new IllegalArgumentException("permissionLink"); } - String path = DocumentClient.joinPath(permissionLink, null); + String path = Utils.joinPath(permissionLink, null); Map requestHeaders = this.getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Permission, path, requestHeaders); return new ResourceResponse(this.doRead(request), Permission.class); @@ -1849,7 +2036,7 @@ public FeedResponse readPermissions(String permissionLink, FeedOptio throw new IllegalArgumentException("permissionLink"); } - String path = DocumentClient.joinPath(permissionLink, Paths.PERMISSIONS_PATH_SEGMENT); + String path = Utils.joinPath(permissionLink, Paths.PERMISSIONS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Permission, path, requestHeaders); return new FeedResponse(new QueryIterable(this, @@ -1899,7 +2086,7 @@ public FeedResponse queryPermissions(String permissionLink, throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(permissionLink, Paths.PERMISSIONS_PATH_SEGMENT); + String path = Utils.joinPath(permissionLink, Paths.PERMISSIONS_PATH_SEGMENT); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Permission, path, @@ -1927,7 +2114,7 @@ public ResourceResponse replaceOffer(Offer offer) throws DocumentClientEx DocumentClient.validateResource(offer); - String path = DocumentClient.joinPath(offer.getSelfLink(), null); + String path = Utils.joinPath(offer.getSelfLink(), null); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Offer, path, offer, @@ -1948,7 +2135,7 @@ public ResourceResponse readOffer(String offerLink) throws DocumentClient throw new IllegalArgumentException("offerLink"); } - String path = DocumentClient.joinPath(offerLink, null); + String path = Utils.joinPath(offerLink, null); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Offer, path, null); return new ResourceResponse(this.doRead(request), Offer.class); } @@ -1960,7 +2147,7 @@ public ResourceResponse readOffer(String offerLink) throws DocumentClient * @return the feed response with the read offers. */ public FeedResponse readOffers(FeedOptions options) { - String path = DocumentClient.joinPath(Paths.OFFERS_PATH_SEGMENT, null); + String path = Utils.joinPath(Paths.OFFERS_PATH_SEGMENT, null); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Offer, path, requestHeaders); return new FeedResponse(new QueryIterable(this, request, ReadType.Feed, Offer.class)); @@ -1993,7 +2180,7 @@ public FeedResponse queryOffers(SqlQuerySpec querySpec, FeedOptions optio throw new IllegalArgumentException("querySpec"); } - String path = DocumentClient.joinPath(Paths.OFFERS_PATH_SEGMENT, null); + String path = Utils.joinPath(Paths.OFFERS_PATH_SEGMENT, null); Map requestHeaders = this.getFeedHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(ResourceType.Offer, path, @@ -2035,6 +2222,24 @@ private DocumentServiceResponse doCreate(DocumentServiceRequest request) throws return response; } + private DocumentServiceResponse doUpsert(DocumentServiceRequest request) throws DocumentClientException { + this.ApplySessionToken(request); + + Map headers = request.getHeaders(); + + // headers can never be null, since it will be initialized even when no request options are specified, + // hence using assertion here instead of exception, being in the private method + assert(headers != null); + + if (headers != null) { + headers.put(HttpConstants.HttpHeaders.IS_UPSERT, "true"); + } + + DocumentServiceResponse response = this.gatewayProxy.doUpsert(request); + this.CaptureSessionToken(request, response); + return response; + } + private DocumentServiceResponse doReplace(DocumentServiceRequest request) throws DocumentClientException { this.ApplySessionToken(request); @@ -2051,7 +2256,7 @@ private DocumentServiceResponse doDelete(DocumentServiceRequest request) throws if (request.getResourceType() != ResourceType.DocumentCollection) { this.CaptureSessionToken(request, response); } else { - this.ClearToken(ResourceId.parse(request.getResourceId())); + this.ClearToken(request, response); } return response; } @@ -2092,7 +2297,7 @@ private void ApplySessionToken(DocumentServiceRequest request) throws DocumentCl // Apply the ambient session. if (!StringUtils.isEmpty(request.getResourceId())) { - String sessionToken = this.sessionContainer.resolveSessionToken(ResourceId.parse(request.getResourceId())); + String sessionToken = this.sessionContainer.resolveSessionToken(request); if (!StringUtils.isEmpty(sessionToken)) { headers.put(HttpConstants.HttpHeaders.SESSION_TOKEN, sessionToken); @@ -2102,15 +2307,11 @@ private void ApplySessionToken(DocumentServiceRequest request) throws DocumentCl private void CaptureSessionToken(DocumentServiceRequest request, DocumentServiceResponse response) throws DocumentClientException { - String sessionToken = response.getResponseHeaders().get(HttpConstants.HttpHeaders.SESSION_TOKEN); - - if (!StringUtils.isEmpty(sessionToken) && !StringUtils.isEmpty(request.getResourceId())) { - this.sessionContainer.setSessionToken(ResourceId.parse(request.getResourceId()), sessionToken); + this.sessionContainer.setSessionToken(request, response); } - } - private void ClearToken(ResourceId resourceId) { - this.sessionContainer.clearToken(resourceId); + private void ClearToken(DocumentServiceRequest request, DocumentServiceResponse response) { + this.sessionContainer.clearToken(request, response); } private Map getRequestHeaders(RequestOptions options) { @@ -2209,30 +2410,6 @@ private Map getMediaHeaders(MediaOptions options) { return requestHeaders; } - private static String joinPath(String path1, String path2) { - path1 = DocumentClient.trimBeginingAndEndingSlashes(path1); - String result = "/" + path1 + "/"; - - if (path2 != null && !path2.isEmpty()) { - path2 = DocumentClient.trimBeginingAndEndingSlashes(path2); - result += path2 + "/"; - } - - return result; - } - - private static String trimBeginingAndEndingSlashes(String path) { - if (path.startsWith("/")) { - path = path.substring(1); - } - - if (path.endsWith("/")) { - path = path.substring(0, path.length() - 1); - } - - return path; - } - private static String serializeProcedureParams(Object[] objectArray) { ObjectMapper mapper = null; String[] stringArray = new String[objectArray.length]; diff --git a/src/com/microsoft/azure/documentdb/DocumentServiceRequest.java b/src/com/microsoft/azure/documentdb/DocumentServiceRequest.java index 7d1c414..254a15b 100644 --- a/src/com/microsoft/azure/documentdb/DocumentServiceRequest.java +++ b/src/com/microsoft/azure/documentdb/DocumentServiceRequest.java @@ -66,6 +66,7 @@ private DocumentServiceRequest(ResourceType resourceType, this.body = body; this.headers = headers != null ? headers : new HashMap(); + this.isNameBased = Utils.isNameBased(path); } /** @@ -232,7 +233,7 @@ static String getAttachmentIdFromMediaId(String mediaId) { // We are cuting off the storage index. byte[] newBuffer = new byte[resoureIdLength]; System.arraycopy(buffer, 0, newBuffer, 0, resoureIdLength); - attachmentId = Helper.encodeBase64String(newBuffer).replace('/', '-'); + attachmentId = Utils.encodeBase64String(newBuffer).replace('/', '-'); } else { attachmentId = mediaId; } @@ -343,4 +344,15 @@ public void setIsMedia(boolean isMedia) { public boolean getIsMedia() { return this.isMedia; } + + private boolean isNameBased = false; + + public boolean getIsNameBased() { + return this.isNameBased; + } + + void setIsNameBased(boolean isNameBased) { + this.isNameBased = isNameBased; + } + } diff --git a/src/com/microsoft/azure/documentdb/GatewayProxy.java b/src/com/microsoft/azure/documentdb/GatewayProxy.java index 0906454..57f52de 100644 --- a/src/com/microsoft/azure/documentdb/GatewayProxy.java +++ b/src/com/microsoft/azure/documentdb/GatewayProxy.java @@ -91,6 +91,11 @@ public DocumentServiceResponse doCreate(DocumentServiceRequest request) throws DocumentClientException { return this.performPostRequest(request); } + + public DocumentServiceResponse doUpsert(DocumentServiceRequest request) + throws DocumentClientException { + return this.performPostRequest(request); + } public DocumentServiceResponse doRead(DocumentServiceRequest request) throws DocumentClientException { @@ -191,8 +196,33 @@ private void putMoreContentIntoDocumentServiceRequest( } if (this.masterKey != null || this.resourceTokens != null) { + String path = request.getPath(); + path = Utils.trimBeginingAndEndingSlashes(path); + + String resourceName = ""; + + if(Utils.isNameBased(path)) { + String[] segments = path.split("/"); + + if (segments.length % 2 == 0) { + // if path has even segments, it is the individual resource like dbs/db1/colls/coll1 + if (Utils.IsResourceType(segments[segments.length-2])) { + resourceName = path; + } + } + else { + // if path has odd segments, get the parent(dbs/db1 from dbs/db1/colls) + if (Utils.IsResourceType(segments[segments.length - 1])) { + resourceName = path.substring(0, path.lastIndexOf("/")); + } + } + } + else { + resourceName = request.getResourceId().toLowerCase(); + } + String authorization = - this.getAuthorizationToken(request.getResourceId(), + this.getAuthorizationToken(resourceName, request.getPath(), request.getResourceType(), httpMethod, diff --git a/src/com/microsoft/azure/documentdb/HashIndex.java b/src/com/microsoft/azure/documentdb/HashIndex.java index 8256b54..fc4ab18 100644 --- a/src/com/microsoft/azure/documentdb/HashIndex.java +++ b/src/com/microsoft/azure/documentdb/HashIndex.java @@ -6,7 +6,19 @@ public final class HashIndex extends Index { /** - * Constructor. + * Specifies an instance of HashIndex class with specified DataType. + * + * Here is an example to instantiate HashIndex class passing in the DataType: + * + *
+     * {@code
+     * 
+     * HashIndex hashIndex = new HashIndex(DataType.String);
+     * 
+     * }
+     * 
+ * + * @param dataType the data type. */ public HashIndex(DataType dataType) { super(IndexKind.Hash); @@ -14,7 +26,29 @@ public HashIndex(DataType dataType) { } /** - * Constructor. + * Initializes a new instance of the HashIndex class with specified DataType and precision. + * + * Here is an example to instantiate HashIndex class passing in the DataType: + * + *
+     * {@code
+     * 
+     * HashIndex hashIndex = new HashIndex(DataType.String, 3);
+     * 
+     * }
+     * 
+ * + * @param dataType the data type. + * @param precision the precision. + */ + public HashIndex(DataType dataType, int precision) { + super(IndexKind.Hash); + this.setDataType(dataType); + this.setPrecision(precision); + } + + /** + * Initializes a new instance of the HashIndex class with json string. * * @param jsonString the json string that represents the index. */ @@ -26,7 +60,7 @@ public HashIndex(String jsonString) { } /** - * Constructor. + * Initializes a new instance of the HashIndex class with json object. * * @param jsonObject the json object that represents the index. */ diff --git a/src/com/microsoft/azure/documentdb/HttpConstants.java b/src/com/microsoft/azure/documentdb/HttpConstants.java index 9d4380e..099cead 100644 --- a/src/com/microsoft/azure/documentdb/HttpConstants.java +++ b/src/com/microsoft/azure/documentdb/HttpConstants.java @@ -144,14 +144,24 @@ public static class HttpHeaders { // Offer header public static final String OFFER_TYPE = "x-ms-offer-type"; + + // Upsert header + public static final String IS_UPSERT = "x-ms-documentdb-is-upsert"; // Index progress headers public static final String INDEX_TRANSFORMATION_PROGRESS = "x-ms-documentdb-collection-index-transformation-progress"; + public static final String LAZY_INDEXING_PROGRESS = "x-ms-documentdb-collection-lazy-indexing-progress"; + + //Owner name + public static final String OWNER_FULL_NAME = "x-ms-alt-content-path"; + + //Owner ID used for name based request in session token. + public static final String OWNER_ID = "x-ms-content-path"; } public static class Versions { - public static String CURRENT_VERSION = "2015-06-03"; - public static String USER_AGENT = "documentdb-java-sdk-1.2.0"; + public static String CURRENT_VERSION = "2015-08-06"; + public static String USER_AGENT = "documentdb-java-sdk-1.4.0"; } public static class StatusCodes { diff --git a/src/com/microsoft/azure/documentdb/IncludedPath.java b/src/com/microsoft/azure/documentdb/IncludedPath.java index 6383e0d..fcec31a 100644 --- a/src/com/microsoft/azure/documentdb/IncludedPath.java +++ b/src/com/microsoft/azure/documentdb/IncludedPath.java @@ -9,14 +9,6 @@ public class IncludedPath extends JsonSerializable { - // default number precisions - private static final short DEFAULT_NUMBER_HASH_PRECISION = 3; - private static final short DEFAULT_NUMBER_RANGE_PRECISION = -1; - - // default string precision - private static final short DEFAULT_STRING_HASH_PRECISION = 3; - private static final short DEFAULT_STRING_RANGE_PRECISION = -1; - private Collection indexes; /** @@ -114,38 +106,12 @@ public void setIndexes(Collection indexes) { @Override void onSave() { - if (this.getIndexes().size() == 0) { - HashIndex hashIndex = new HashIndex(DataType.String); - hashIndex.setPrecision(IncludedPath.DEFAULT_STRING_HASH_PRECISION); - this.indexes.add(hashIndex); - - RangeIndex rangeIndex = new RangeIndex(DataType.Number); - rangeIndex.setPrecision(IncludedPath.DEFAULT_NUMBER_RANGE_PRECISION); - this.indexes.add(rangeIndex); - } - - for (Index index : this.indexes) { - if (index.getKind() == IndexKind.Hash) { - HashIndex hashIndex = (HashIndex)index; - if (!hashIndex.hasPrecision()) { - if(hashIndex.getDataType() == DataType.Number) { - hashIndex.setPrecision(IncludedPath.DEFAULT_NUMBER_HASH_PRECISION); - } else if(hashIndex.getDataType() == DataType.String) { - hashIndex.setPrecision(IncludedPath.DEFAULT_STRING_HASH_PRECISION); - } - } - } else if(index.getKind() == IndexKind.Range) { - RangeIndex rangeIndex = (RangeIndex)index; - if (!rangeIndex.hasPrecision()) { - if (rangeIndex.getDataType() == DataType.Number) { - rangeIndex.setPrecision(IncludedPath.DEFAULT_NUMBER_RANGE_PRECISION); - } else if (rangeIndex.getDataType() == DataType.String) { - rangeIndex.setPrecision(IncludedPath.DEFAULT_STRING_RANGE_PRECISION); - } - } + if (this.indexes != null) { + for (Index index : this.indexes) { + index.onSave(); } - } - super.set(Constants.Properties.INDEXES, this.indexes); + super.set(Constants.Properties.INDEXES, this.indexes); + } } } diff --git a/src/com/microsoft/azure/documentdb/Index.java b/src/com/microsoft/azure/documentdb/Index.java index 3e525b6..eca8d62 100644 --- a/src/com/microsoft/azure/documentdb/Index.java +++ b/src/com/microsoft/azure/documentdb/Index.java @@ -56,4 +56,93 @@ public IndexKind getKind() { private void setKind(IndexKind indexKind) { super.set(Constants.Properties.INDEX_KIND, indexKind.name()); } + + /** + * Returns an instance of RangeIndex class with specified DataType. + * + * Here is an example to create RangeIndex instance passing in the DataType: + * + *
+     * {@code
+     * 
+     * RangeIndex rangeIndex = Index.Range(DataType.Number);
+     * 
+     * }
+     * 
+ * + * @param dataType the data type. + * @return an instance of RangeIndex type. + */ + public static RangeIndex Range(DataType dataType) { + return new RangeIndex(dataType); + } + + /** + * Returns an instance of RangeIndex class with specified DataType and precision. + * + * Here is an example to create RangeIndex instance passing in the DataType and precision: + * + *
+     * {@code
+     * 
+     * RangeIndex rangeIndex = Index.Range(DataType.Number, -1);
+     * 
+     * }
+     * 
+ * + * @param dataType specifies the target data type for the index path specification. + * @param precision specifies the precision to be used for the data type associated with this index. + * @return an instance of RangeIndex type. + */ + public static RangeIndex Range(DataType dataType, int precision) { + return new RangeIndex(dataType, precision); + } + + /** + * Returns an instance of HashIndex class with specified DataType. + * + * Here is an example to create HashIndex instance passing in the DataType: + * + *
+     * {@code
+     * 
+     * HashIndex hashIndex = Index.Hash(DataType.String);
+     * } 
+     * 
+ * + * @param dataType specifies the target data type for the index path specification. + * @return an instance of HashIndex type. + */ + public static HashIndex Hash(DataType dataType) { + return new HashIndex(dataType); + } + + /** + * Returns an instance of HashIndex class with specified DataType and precision. + * + * Here is an example to create HashIndex instance passing in the DataType and precision: + * + * HashIndex hashIndex = Index.Hash(DataType.String, 3); + * + * @param dataType specifies the target data type for the index path specification. + * @param precision specifies the precision to be used for the data type associated with this index. + * @return an instance of HashIndex type. + */ + public static HashIndex Hash(DataType dataType, int precision) { + return new HashIndex(dataType, precision); + } + + /** + * Returns an instance of SpatialIndex class with specified DataType. + * + * Here is an example to create SpatialIndex instance passing in the DataType: + * + * SpatialIndex spatialIndex = Index.Spatial(DataType.Point); + * + * @param dataType specifies the target data type for the index path specification. + * @return an instance of SpatialIndex type. + */ + public static SpatialIndex Spatial(DataType dataType) { + return new SpatialIndex(dataType); + } } diff --git a/src/com/microsoft/azure/documentdb/IndexingPolicy.java b/src/com/microsoft/azure/documentdb/IndexingPolicy.java index 8c9a317..81df558 100644 --- a/src/com/microsoft/azure/documentdb/IndexingPolicy.java +++ b/src/com/microsoft/azure/documentdb/IndexingPolicy.java @@ -1,5 +1,6 @@ package com.microsoft.azure.documentdb; +import java.util.Arrays; import java.util.ArrayList; import java.util.Collection; @@ -26,8 +27,49 @@ public IndexingPolicy() { } /** - * Constructor. + * Initializes a new instance of the IndexingPolicy class with the specified set of indexes as + * default index specifications for the root path. + * + * The following example shows how to override the default indexingPolicy for root path: + * + *
+     * {@code
+     * HashIndex hashIndexOverride = Index.Hash(DataType.String, 5);
+     * RangeIndex rangeIndexOverride = Index.Range(DataType.Number, 2);
+     * SpatialIndex spatialIndexOverride = Index.Spatial(DataType.Point);
+     *
+     * IndexingPolicy indexingPolicy = new IndexingPolicy(hashIndexOverride, rangeIndexOverride, spatialIndexOverride);
+     * }
+     * 
+ * + * If you would like to just override the indexingPolicy for Numbers you can specify just that: * + *
+     * {@code
+     * RangeIndex rangeIndexOverride = Index.Range(DataType.Number, 2);
+     *
+     * IndexingPolicy indexingPolicy = new IndexingPolicy(rangeIndexOverride);
+     * }
+     * 
+ * + * @param defaultIndexOverrides comma separated set of indexes that serve as default index specifications for the root path. + */ + public IndexingPolicy(Index[] defaultIndexOverrides) { + this(); + + if (defaultIndexOverrides == null) { + throw new IllegalArgumentException("defaultIndexOverrides is null."); + } + + IncludedPath includedPath = new IncludedPath(); + includedPath.setPath(IndexingPolicy.DEFAULT_PATH); + includedPath.setIndexes(new ArrayList(Arrays.asList(defaultIndexOverrides))); + this.getIncludedPaths().add(includedPath); +} + + /** + * Constructor. + * * @param jsonString the json string that represents the indexing policy. */ public IndexingPolicy(String jsonString) { @@ -36,7 +78,7 @@ public IndexingPolicy(String jsonString) { /** * Constructor. - * + * * @param jsonObject the json object that represents the indexing policy. */ public IndexingPolicy(JSONObject jsonObject) { diff --git a/src/com/microsoft/azure/documentdb/RangeIndex.java b/src/com/microsoft/azure/documentdb/RangeIndex.java index c7f92c5..88a56b1 100644 --- a/src/com/microsoft/azure/documentdb/RangeIndex.java +++ b/src/com/microsoft/azure/documentdb/RangeIndex.java @@ -6,7 +6,19 @@ public final class RangeIndex extends Index { /** - * Constructor. + * Initializes a new instance of the RangeIndex class with specified DataType. + * + * Here is an example to instantiate RangeIndex class passing in the DataType: + * + *
+     * {@code
+     * 
+     * RangeIndex rangeIndex = new RangeIndex(DataType.Number);
+     * 
+     * } 
+     * 
+ * + * @param dataType the data type. */ public RangeIndex(DataType dataType) { super(IndexKind.Range); @@ -14,7 +26,25 @@ public RangeIndex(DataType dataType) { } /** - * Constructor. + * Initializes a new instance of the RangeIndex class with specified DataType and precision. + * + *
+     * {@code
+     * 
+     * RangeIndex rangeIndex = new RangeIndex(DataType.Number, -1);
+     * 
+     * } 
+     * 
+ * + */ + public RangeIndex(DataType dataType, int precision) { + super(IndexKind.Range); + this.setDataType(dataType); + this.setPrecision(precision); + } + + /** + * Initializes a new instance of the RangeIndex class with json string. * * @param jsonString the json string that represents the index. */ @@ -26,7 +56,7 @@ public RangeIndex(String jsonString) { } /** - * Constructor. + * Initializes a new instance of the RangeIndex class with json object. * * @param jsonObject the json object that represents the index. */ diff --git a/src/com/microsoft/azure/documentdb/ResourceId.java b/src/com/microsoft/azure/documentdb/ResourceId.java index 4fd1faf..96287fc 100644 --- a/src/com/microsoft/azure/documentdb/ResourceId.java +++ b/src/com/microsoft/azure/documentdb/ResourceId.java @@ -392,7 +392,7 @@ static String toBase64String(byte[] buffer) { static String toBase64String(byte[] buffer, int offset, int length) { byte[] subBuffer = Arrays.copyOfRange(buffer, offset, length); - return Helper.encodeBase64String(subBuffer).replace('/', '-'); + return Utils.encodeBase64String(subBuffer).replace('/', '-'); } // Copy the bytes provided with a for loop, faster when there are only a few diff --git a/src/com/microsoft/azure/documentdb/ResourceResponse.java b/src/com/microsoft/azure/documentdb/ResourceResponse.java index 0a4c0d0..8008ca1 100644 --- a/src/com/microsoft/azure/documentdb/ResourceResponse.java +++ b/src/com/microsoft/azure/documentdb/ResourceResponse.java @@ -282,6 +282,19 @@ public long getIndexTransformationProgress() { return Long.valueOf(value); } + /** + * Gets the progress of lazy indexing. + * + * @return the progress of lazy indexing. + */ + public long getLazyIndexingProgress() { + String value = this.getResponseHeaders().get(HttpConstants.HttpHeaders.LAZY_INDEXING_PROGRESS); + if (StringUtils.isEmpty(value)) { + return -1; + } + return Long.valueOf(value); + } + /** * Deprecated. */ diff --git a/src/com/microsoft/azure/documentdb/SessionContainer.java b/src/com/microsoft/azure/documentdb/SessionContainer.java index f9373eb..9d858b1 100644 --- a/src/com/microsoft/azure/documentdb/SessionContainer.java +++ b/src/com/microsoft/azure/documentdb/SessionContainer.java @@ -10,48 +10,122 @@ final class SessionContainer { private final ConcurrentHashMap sessionTokens; + private final ConcurrentHashMap sessionTokensNameBased; private final String hostName; public SessionContainer(final String hostName) { this.hostName = hostName; this.sessionTokens = new ConcurrentHashMap(); + this.sessionTokensNameBased = new ConcurrentHashMap(); } public String getHostName() { return this.hostName; } - public String resolveSessionToken(final ResourceId resourceId) { - if (resourceId.getDocumentCollection() != 0) // One token per collection. - { - return this.sessionTokens.get(resourceId.getUniqueDocumentCollectionId()); - + public String resolveSessionToken(final DocumentServiceRequest request) { + if(!request.getIsNameBased()) { + if(!StringUtils.isEmpty(request.getResourceId())) { + ResourceId resourceId = ResourceId.parse(request.getResourceId()); + if (resourceId.getDocumentCollection() != 0) {// One token per collection. + return this.sessionTokens.get(resourceId.getUniqueDocumentCollectionId()); + } + } + } + else { + String collectionName = getCollectionName(request.getPath()); + if(!StringUtils.isEmpty(collectionName)) { + return this.sessionTokensNameBased.get(collectionName); + } } return null; } - public void clearToken(final ResourceId resourceId) { - if (resourceId.getDocumentCollection() != 0) { - this.sessionTokens.remove(resourceId.getUniqueDocumentCollectionId()); + public void clearToken(final DocumentServiceRequest request, final DocumentServiceResponse response) + { + String ownerFullName = response.getResponseHeaders().get(HttpConstants.HttpHeaders.OWNER_FULL_NAME); + String ownerId = response.getResponseHeaders().get(HttpConstants.HttpHeaders.OWNER_ID); + + String collectionName = getCollectionName(ownerFullName); + + if (!request.getIsNameBased()) { + ownerId = request.getResourceId(); + } + + if (!StringUtils.isEmpty(ownerId)) { + ResourceId resourceId = ResourceId.parse(ownerId); + if (resourceId.getDocumentCollection() != 0 && !StringUtils.isEmpty(collectionName)) { + this.sessionTokens.remove(resourceId.getUniqueDocumentCollectionId()); + this.sessionTokensNameBased.remove(collectionName); + } } } - public void setSessionToken(ResourceId resourceId, String token) { - if (resourceId.getDocumentCollection() != 0) { - long currentTokenValue = !StringUtils.isEmpty(token) ? Long.parseLong(token) : 0; + public void setSessionToken(DocumentServiceRequest request, DocumentServiceResponse response) + { + String sessionToken = response.getResponseHeaders().get(HttpConstants.HttpHeaders.SESSION_TOKEN); + + if (!StringUtils.isEmpty(sessionToken)) { + String ownerFullName = response.getResponseHeaders().get(HttpConstants.HttpHeaders.OWNER_FULL_NAME); + String ownerId = response.getResponseHeaders().get(HttpConstants.HttpHeaders.OWNER_ID); - String oldToken = this.sessionTokens.get(resourceId.getUniqueDocumentCollectionId()); + String collectionName = getCollectionName(ownerFullName); - if (oldToken == null) { - this.sessionTokens.putIfAbsent(resourceId.getUniqueDocumentCollectionId(), token); - } else { - long existingValue = Long.parseLong(oldToken); - if (existingValue < currentTokenValue) { - this.sessionTokens.put(resourceId.getUniqueDocumentCollectionId(), token); + if (!request.getIsNameBased()) { + ownerId = request.getResourceId(); + } + + if (!StringUtils.isEmpty(ownerId)) { + ResourceId resourceId = ResourceId.parse(ownerId); + + if (resourceId.getDocumentCollection() != 0 && !StringUtils.isEmpty(collectionName)) { + long currentTokenValue = !StringUtils.isEmpty(sessionToken) ? Long.parseLong(sessionToken) : 0; + + String oldToken = this.sessionTokens.get(resourceId.getUniqueDocumentCollectionId()); + + if (oldToken == null) { + this.sessionTokens.putIfAbsent(resourceId.getUniqueDocumentCollectionId(), sessionToken); + } + else { + long existingValue = Long.parseLong(oldToken); + if (existingValue < currentTokenValue) { + this.sessionTokens.put(resourceId.getUniqueDocumentCollectionId(), sessionToken); + } + } + + String oldTokenNameBased = this.sessionTokensNameBased.get(collectionName); + + if (oldTokenNameBased == null) { + this.sessionTokensNameBased.putIfAbsent(collectionName, sessionToken); + } + else { + long existingValue = Long.parseLong(oldTokenNameBased); + if (existingValue < currentTokenValue) { + this.sessionTokensNameBased.put(collectionName, sessionToken); + } + } } } + } + } + + private String getCollectionName(String resourceFullName) + { + if (resourceFullName != null) + { + resourceFullName = Utils.trimBeginingAndEndingSlashes(resourceFullName); + int slashCount=0; + for(int i=0; i< resourceFullName.length(); i++) + { + if(resourceFullName.charAt(i) == '/') { + slashCount++; + if(slashCount == 4) { + return resourceFullName.substring(0, i); + } + } + } } + return resourceFullName; } - } diff --git a/src/com/microsoft/azure/documentdb/SpatialIndex.java b/src/com/microsoft/azure/documentdb/SpatialIndex.java index be42920..d5e0a16 100644 --- a/src/com/microsoft/azure/documentdb/SpatialIndex.java +++ b/src/com/microsoft/azure/documentdb/SpatialIndex.java @@ -6,7 +6,19 @@ public final class SpatialIndex extends Index { /** - * Constructor. + * Initializes a new instance of the SpatialIndex class. + * + * Here is an example to instantiate SpatialIndex class passing in the DataType + * + *
+     * {@code
+     * 
+     * SpatialIndex spatialIndex = new SpatialIndex(DataType.Point);
+     * 
+     * }
+     * 
+ * + * @param dataType specifies the target data type for the index path specification. */ public SpatialIndex(DataType dataType) { super(IndexKind.Spatial); @@ -14,7 +26,7 @@ public SpatialIndex(DataType dataType) { } /** - * Constructor. + * Initializes a new instance of the SpatialIndex class. * * @param jsonString the json string that represents the index. */ @@ -26,7 +38,7 @@ public SpatialIndex(String jsonString) { } /** - * Constructor. + * Initializes a new instance of the SpatialIndex class. * * @param jsonObject the json object that represents the index. */ diff --git a/src/com/microsoft/azure/documentdb/Utils.java b/src/com/microsoft/azure/documentdb/Utils.java new file mode 100644 index 0000000..6a06a6b --- /dev/null +++ b/src/com/microsoft/azure/documentdb/Utils.java @@ -0,0 +1,129 @@ +package com.microsoft.azure.documentdb; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; + +class Utils { + static String encodeBase64String(byte[] binaryData) { + String encodedString = Base64.encodeBase64String(binaryData); + + if (encodedString.endsWith("\r\n")) { + encodedString = encodedString.substring(0, encodedString.length() - 2); + } + return encodedString; + } + + /** + * Checks whether the specified link is Name based or not + * + * @param link the link to analyze. + * @return true or false + */ + static boolean isNameBased(String link) { + if(StringUtils.isEmpty(link)) { + return false; + } + + // trimming the leading "/" + if (link.startsWith("/") && link.length() > 1) { + link = link.substring(1); + } + + // Splitting the link(separated by "/") into parts + String[] parts = link.split("/"); + + // First part should be "dbs" + if(parts.length == 0 || StringUtils.isEmpty(parts[0]) || !parts[0].equalsIgnoreCase(Paths.DATABASES_PATH_SEGMENT)) { + return false; + } + + // The second part is the database id(ResourceID or Name) and cannot be empty + if(parts.length < 2 || StringUtils.isEmpty(parts[1])) { + return false; + } + + // Either ResourceID or database name + String databaseID = parts[1]; + + // Length of databaseID(in case of ResourceID) is always 8 + if (databaseID.length() != 8) { + return true; + } + + // Decoding the databaseID + byte[] buffer = ResourceId.fromBase64String(databaseID); + + // Length of decoded buffer(in case of ResourceID) is always 4 + if(buffer.length != 4) { + return true; + } + + return false; + } + + /** + * Checks whether the specified path segment is a resource type + * + * @param resourcePathSegment the path segment to analyze. + * @return true or false + */ + static boolean IsResourceType(String resourcePathSegment) { + if (StringUtils.isEmpty(resourcePathSegment)) { + return false; + } + + switch (resourcePathSegment.toLowerCase()) { + case Paths.ATTACHMENTS_PATH_SEGMENT: + case Paths.COLLECTIONS_PATH_SEGMENT: + case Paths.DATABASES_PATH_SEGMENT: + case Paths.PERMISSIONS_PATH_SEGMENT: + case Paths.USERS_PATH_SEGMENT: + case Paths.DOCUMENTS_PATH_SEGMENT: + case Paths.STORED_PROCEDURES_PATH_SEGMENT: + case Paths.TRIGGERS_PATH_SEGMENT: + case Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT: + case Paths.CONFLICTS_PATH_SEGMENT: + return true; + + default: + return false; + } + } + + /** + * Joins the specified paths by appropriately padding them with '/' + * + * @param path1 the first path segment to join. + * @param path2 the second path segment to join. + * @return the concatenated path with '/' + */ + static String joinPath(String path1, String path2) { + path1 = trimBeginingAndEndingSlashes(path1); + String result = "/" + path1 + "/"; + + if(!StringUtils.isEmpty(path2)) { + path2 = trimBeginingAndEndingSlashes(path2); + result += path2 + "/"; + } + + return result; + } + + /** + * Trims the beginning and ending '/' from the given path + * + * @param path the path to trim for beginning and ending slashes + * @return the path without beginning and ending '/' + */ + static String trimBeginingAndEndingSlashes(String path) { + if (path.startsWith("/")) { + path = path.substring(1); + } + + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + return path; + } +} diff --git a/src/com/microsoft/azure/documentdb/test/GatewayTests.java b/src/com/microsoft/azure/documentdb/test/GatewayTests.java index d2c5119..a682615 100644 --- a/src/com/microsoft/azure/documentdb/test/GatewayTests.java +++ b/src/com/microsoft/azure/documentdb/test/GatewayTests.java @@ -6,7 +6,6 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.UUID; @@ -26,6 +25,7 @@ import com.microsoft.azure.documentdb.AccessCondition; import com.microsoft.azure.documentdb.AccessConditionType; import com.microsoft.azure.documentdb.Attachment; +import com.microsoft.azure.documentdb.Conflict; import com.microsoft.azure.documentdb.ConnectionPolicy; import com.microsoft.azure.documentdb.ConsistencyLevel; import com.microsoft.azure.documentdb.DataType; @@ -68,8 +68,8 @@ final class AnotherPOJO { } public final class GatewayTests { - static final String HOST = "[YOUR_ENDPOINT_HERE]"; - static final String MASTER_KEY = "[YOUR_KEY_HERE]"; + static final String MASTER_KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; + static final String HOST = "https://localhost:443"; private Database databaseForTest; private DocumentCollection collectionForTest; @@ -77,6 +77,17 @@ public final class GatewayTests { private static final String databaseForTestAlternativeId1 = "GatewayTests_database1"; private static final String databaseForTestAlternativeId2 = "GatewayTests_database2"; + static final String DATABASES_PATH_SEGMENT = "dbs"; + static final String USERS_PATH_SEGMENT = "users"; + static final String PERMISSIONS_PATH_SEGMENT = "permissions"; + static final String COLLECTIONS_PATH_SEGMENT = "colls"; + static final String DOCUMENTS_PATH_SEGMENT = "docs"; + static final String ATTACHMENTS_PATH_SEGMENT = "attachments"; + static final String STORED_PROCEDURES_PATH_SEGMENT = "sprocs"; + static final String TRIGGERS_PATH_SEGMENT = "triggers"; + static final String USER_DEFINED_FUNCTIONS_PATH_SEGMENT = "udfs"; + static final String CONFLICTS_PATH_SEGMENT = "conflicts"; + public class Retry implements TestRule { private int retryCount; @@ -143,10 +154,10 @@ void cleanUpGeneratedDatabases() throws DocumentClientException { for (String id : allDatabaseIds) { Database database = client.queryDatabases( new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter("@id", id))), + new SqlParameterCollection(new SqlParameter("@id", id))), null).getQueryIterable().iterator().next(); if (database != null) { - client.deleteDatabase(database.getSelfLink(), null); + client.deleteDatabase(getDatabaseLink(database, true), null); } } } @@ -154,9 +165,9 @@ void cleanUpGeneratedDatabases() throws DocumentClientException { @Before public void setUp() throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Clean up before setting up in case a previous running fails to tear down. this.cleanUpGeneratedDatabases(); @@ -169,9 +180,9 @@ public void setUp() throws DocumentClientException { DocumentCollection collectionDefinition = new DocumentCollection(); collectionDefinition.setId(GatewayTests.getUID()); - this.collectionForTest = client.createCollection(this.databaseForTest.getSelfLink(), - collectionDefinition, - null).getResource(); + this.collectionForTest = client.createCollection(getDatabaseLink(this.databaseForTest, true), + collectionDefinition, + null).getResource(); } @After @@ -244,31 +255,31 @@ public void testJsonSerialization() { Document expectedDocument = new Document( "{" + - " 'prop0': null," + - " 'prop1': 'abc'," + - " 'child1': {" + - " 'child1Prop1': 500" + - " }," + - " 'child2': {" + - " 'child2Prop1': '800'" + - " }," + - " 'child3': {" + - " 'pojoProp': '456'" + - " }," + - " 'child4': {" + - " 'pojoProp': '789'" + - " }," + - " 'collection1': [101, 102]," + - " 'collection2': [{'foo': 'bar'}]," + - " 'collection3': [{'pojoProp': '456'}]," + - " 'collection4': [[['ABCD']]]" + + " 'prop0': null," + + " 'prop1': 'abc'," + + " 'child1': {" + + " 'child1Prop1': 500" + + " }," + + " 'child2': {" + + " 'child2Prop1': '800'" + + " }," + + " 'child3': {" + + " 'pojoProp': '456'" + + " }," + + " 'child4': {" + + " 'pojoProp': '789'" + + " }," + + " 'collection1': [101, 102]," + + " 'collection2': [{'foo': 'bar'}]," + + " 'collection3': [{'pojoProp': '456'}]," + + " 'collection4': [[['ABCD']]]" + "}"); Assert.assertEquals(expectedDocument.toString(), document.toString()); Assert.assertEquals("456", document.getObject("child3", StaticPOJOForTest.class).pojoProp); Assert.assertEquals("789", document.getObject("child4", AnotherPOJO.class).pojoProp); Assert.assertEquals("456", document.getCollection("collection3", - StaticPOJOForTest.class).iterator().next().pojoProp); + StaticPOJOForTest.class).iterator().next().pojoProp); document = new Document("{'pojoProp': '654'}"); StaticPOJOForTest pojo = document.toObject(StaticPOJOForTest.class); @@ -283,46 +294,57 @@ public void testSqlQuerySpecSerialization() { Assert.assertEquals("{\"query\":\"SELECT 1\"}", (new SqlQuerySpec("SELECT 1")).toString()); Assert.assertEquals("{\"query\":\"SELECT 1\",\"parameters\":[" + - "{\"name\":\"@p1\",\"value\":5}" + - "]}", - (new SqlQuerySpec("SELECT 1", - new SqlParameterCollection(new SqlParameter("@p1", 5)))).toString()); + "{\"name\":\"@p1\",\"value\":5}" + + "]}", + (new SqlQuerySpec("SELECT 1", + new SqlParameterCollection(new SqlParameter("@p1", 5)))).toString()); Assert.assertEquals("{\"query\":\"SELECT 1\",\"parameters\":[" + - "{\"name\":\"@p1\",\"value\":5}," + - "{\"name\":\"@p1\",\"value\":true}" + - "]}", - (new SqlQuerySpec("SELECT 1", - new SqlParameterCollection(new SqlParameter("@p1", 5), - new SqlParameter("@p1", true)))).toString()); + "{\"name\":\"@p1\",\"value\":5}," + + "{\"name\":\"@p1\",\"value\":true}" + + "]}", + (new SqlQuerySpec("SELECT 1", + new SqlParameterCollection(new SqlParameter("@p1", 5), + new SqlParameter("@p1", true)))).toString()); Assert.assertEquals("{\"query\":\"SELECT 1\",\"parameters\":[" + - "{\"name\":\"@p1\",\"value\":\"abc\"}" + - "]}", - (new SqlQuerySpec("SELECT 1", - new SqlParameterCollection(new SqlParameter("@p1", "abc")))).toString()); + "{\"name\":\"@p1\",\"value\":\"abc\"}" + + "]}", + (new SqlQuerySpec("SELECT 1", + new SqlParameterCollection(new SqlParameter("@p1", "abc")))).toString()); Assert.assertEquals("{\"query\":\"SELECT 1\",\"parameters\":[" + - "{\"name\":\"@p1\",\"value\":[1,2,3]}" + - "]}", - (new SqlQuerySpec("SELECT 1", - new SqlParameterCollection( - new SqlParameter("@p1", new int[] {1,2,3})))).toString()); + "{\"name\":\"@p1\",\"value\":[1,2,3]}" + + "]}", + (new SqlQuerySpec("SELECT 1", + new SqlParameterCollection( + new SqlParameter("@p1", new int[] {1,2,3})))).toString()); Assert.assertEquals("{\"query\":\"SELECT 1\",\"parameters\":[" + - "{\"name\":\"@p1\",\"value\":{\"a\":[1,2,3]}}" + - "]}", - (new SqlQuerySpec("SELECT 1", - new SqlParameterCollection(new SqlParameter( - "@p1", new JSONObject("{\"a\":[1,2,3]}"))))).toString()); + "{\"name\":\"@p1\",\"value\":{\"a\":[1,2,3]}}" + + "]}", + (new SqlQuerySpec("SELECT 1", + new SqlParameterCollection(new SqlParameter( + "@p1", new JSONObject("{\"a\":[1,2,3]}"))))).toString()); + } + + @Test + public void testDatabaseCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testDatabaseCrud(isNameBased); } @Test - public void testDatabaseCrud() throws DocumentClientException { + public void testDatabaseCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testDatabaseCrud(isNameBased); + } + + private void testDatabaseCrud(boolean isNameBased) throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Read databases. List databases = client.readDatabases(null).getQueryIterable().toList(); @@ -339,18 +361,18 @@ public void testDatabaseCrud() throws DocumentClientException { Assert.assertEquals(beforeCreateDatabasesCount + 1, databases.size()); // query databases. databases = client.queryDatabases(new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter( - "@id", databaseDefinition.getId()))), - null).getQueryIterable().toList(); + new SqlParameterCollection(new SqlParameter( + "@id", databaseDefinition.getId()))), + null).getQueryIterable().toList(); // number of results for the query should be > 0. Assert.assertTrue(databases.size() > 0); - + // Delete database. - client.deleteDatabase(createdDb.getSelfLink(), null); + client.deleteDatabase(getDatabaseLink(createdDb, isNameBased), null); // Read database after deletion. try { - client.readDatabase(createdDb.getSelfLink(), null); + client.readDatabase(getDatabaseLink(createdDb, isNameBased), null); Assert.fail("Exception didn't happen."); } catch (DocumentClientException e) { Assert.assertEquals(404, e.getStatusCode()); @@ -359,36 +381,47 @@ public void testDatabaseCrud() throws DocumentClientException { } @Test - public void testCollectionCrud() throws DocumentClientException { + public void testCollectionCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testCollectionCrud(isNameBased); + } + + @Test + public void testCollectionCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testCollectionCrud(isNameBased); + } + + private void testCollectionCrud(boolean isNameBased) throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); - List collections = client.readCollections(this.databaseForTest.getSelfLink(), - null).getQueryIterable().toList(); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + List collections = client.readCollections(this.getDatabaseLink(this.databaseForTest, isNameBased), + null).getQueryIterable().toList(); // Create a collection. int beforeCreateCollectionsCount = collections.size(); - + IndexingPolicy consistentPolicy = new IndexingPolicy("{'indexingMode': 'Consistent'}"); DocumentCollection collectionDefinition = new DocumentCollection(); collectionDefinition.setId(GatewayTests.getUID()); collectionDefinition.setIndexingPolicy(consistentPolicy); - DocumentCollection createdCollection = client.createCollection(this.databaseForTest.getSelfLink(), - collectionDefinition, - null).getResource(); + DocumentCollection createdCollection = client.createCollection(this.getDatabaseLink(this.databaseForTest, isNameBased), + collectionDefinition, + null).getResource(); Assert.assertEquals(collectionDefinition.getId(), createdCollection.getId()); Assert.assertEquals(IndexingMode.Consistent, createdCollection.getIndexingPolicy().getIndexingMode()); // Read collections after creation. - collections = client.readCollections(this.databaseForTest.getSelfLink(), null).getQueryIterable().toList(); + collections = client.readCollections(this.getDatabaseLink(this.databaseForTest, isNameBased), null).getQueryIterable().toList(); // Create should increase the number of collections. Assert.assertEquals(collections.size(), beforeCreateCollectionsCount + 1); // Query collections. - collections = client.queryCollections(this.databaseForTest.getSelfLink(), - new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter( - "@id", collectionDefinition.getId()))), - null).getQueryIterable().toList(); + collections = client.queryCollections(this.getDatabaseLink(this.databaseForTest, isNameBased), + new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", + new SqlParameterCollection(new SqlParameter( + "@id", collectionDefinition.getId()))), + null).getQueryIterable().toList(); Assert.assertTrue(collections.size() > 0); // Replacing indexing policy is allowed. @@ -408,12 +441,15 @@ public void testCollectionCrud() throws DocumentClientException { Assert.assertEquals("BadRequest", e.getError().getCode()); } + // Resetting the id of the createdCollection so that it can be deleted with the named based id + createdCollection.setId(collectionDefinition.getId()); + // Delete collection. - client.deleteCollection(createdCollection.getSelfLink(), null); + client.deleteCollection(this.getDocumentCollectionLink(this.databaseForTest, createdCollection, isNameBased), null); // Read collection after deletion. try { - client.readCollection(createdCollection.getSelfLink(), null); + client.readCollection(this.getDocumentCollectionLink(this.databaseForTest, createdCollection, isNameBased), null); Assert.fail("Exception didn't happen."); } catch (DocumentClientException e) { Assert.assertEquals(404, e.getStatusCode()); @@ -424,9 +460,9 @@ public void testCollectionCrud() throws DocumentClientException { @Test public void testSpatialIndex() throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); DocumentCollection collectionDefinition = new DocumentCollection(); collectionDefinition.setId(GatewayTests.getUID()); IndexingPolicy indexingPolicy = new IndexingPolicy(); @@ -448,7 +484,7 @@ public void testSpatialIndex() throws DocumentClientException { Document location2 = new Document( "{ 'id': 'loc2', 'Location': { 'type': 'Point', 'coordinates': [ 100.0, 100.0 ] } }"); client.createDocument(collection.getSelfLink(), location2, null, true); - + List results = client.queryDocuments( collection.getSelfLink(), "SELECT * FROM root WHERE (ST_DISTANCE(root.Location, {type: 'Point', coordinates: [20.1, 20]}) < 20000) ", @@ -460,11 +496,11 @@ public void testSpatialIndex() throws DocumentClientException { @Test public void testQueryIterableCrud() throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); List documents = client.readDocuments(this.collectionForTest.getSelfLink(), - null).getQueryIterable().toList(); + null).getQueryIterable().toList(); final int numOfDocuments = 10; // Create 10 documents. @@ -505,7 +541,7 @@ public void testQueryIterableCrud() throws DocumentClientException { try { Thread.sleep(1000); } catch (InterruptedException e) { - + } } while (continuationToken != null); @@ -530,65 +566,65 @@ public void testQueryIterableCrud() throws DocumentClientException { @Test public void testCollectionIndexingPolicy() throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Default indexing mode should be consistent. Assert.assertEquals(IndexingMode.Consistent, this.collectionForTest.getIndexingPolicy().getIndexingMode()); DocumentCollection lazyCollectionDefinition = new DocumentCollection( "{" + - " 'id': 'lazy collection'," + - " 'indexingPolicy': {" + - " 'indexingMode': 'lazy'" + - " }" + + " 'id': 'lazy collection'," + + " 'indexingPolicy': {" + + " 'indexingMode': 'lazy'" + + " }" + "}"); client.deleteCollection(this.collectionForTest.getSelfLink(), null); DocumentCollection lazyCollection = client.createCollection(this.databaseForTest.getSelfLink(), - lazyCollectionDefinition, - null).getResource(); + lazyCollectionDefinition, + null).getResource(); // Indexing mode should be lazy. Assert.assertEquals(IndexingMode.Lazy, lazyCollection.getIndexingPolicy().getIndexingMode()); DocumentCollection consistentCollectionDefinition = new DocumentCollection( "{" + - " 'id': 'lazy collection'," + - " 'indexingPolicy': {" + - " 'indexingMode': 'Consistent'" + - " }" + + " 'id': 'lazy collection'," + + " 'indexingPolicy': {" + + " 'indexingMode': 'Consistent'" + + " }" + "}"); client.deleteCollection(lazyCollection.getSelfLink(), null); DocumentCollection consistentCollection = client.createCollection(this.databaseForTest.getSelfLink(), - consistentCollectionDefinition, - null).getResource(); + consistentCollectionDefinition, + null).getResource(); // Indexing mode should be consistent. Assert.assertEquals(this.collectionForTest.getIndexingPolicy().getIndexingMode(), - IndexingMode.Consistent); + IndexingMode.Consistent); DocumentCollection collectionDefinition = new DocumentCollection( "{" + - " 'id': 'CollectionWithIndexingPolicy'," + - " 'indexingPolicy': {" + - " 'automatic': true," + - " 'indexingMode': 'Consistent'," + - " 'includedPaths': ["+ - " {" + - " 'path': '/'," + - " 'indexes': [" + - " {" + - " 'kind': 'Hash'," + - " 'dataType': 'Number'," + - " 'precision': 2," + - " }" + - " ]" + - " }" + - " ]," + - " 'excludedPaths': [" + - " {" + - " 'path': '/\"systemMetadata\"/*'," + - " }" + - " ]" + - " }" + + " 'id': 'CollectionWithIndexingPolicy'," + + " 'indexingPolicy': {" + + " 'automatic': true," + + " 'indexingMode': 'Consistent'," + + " 'includedPaths': ["+ + " {" + + " 'path': '/'," + + " 'indexes': [" + + " {" + + " 'kind': 'Hash'," + + " 'dataType': 'Number'," + + " 'precision': 2," + + " }" + + " ]" + + " }" + + " ]," + + " 'excludedPaths': [" + + " {" + + " 'path': '/\"systemMetadata\"/*'," + + " }" + + " ]" + + " }" + "}"); // Change the index using the setter. @@ -599,8 +635,8 @@ public void testCollectionIndexingPolicy() throws DocumentClientException { client.deleteCollection(consistentCollection.getSelfLink(), null); DocumentCollection collectionWithSecondaryIndex = client.createCollection(this.databaseForTest.getSelfLink(), - collectionDefinition, - null).getResource(); + collectionDefinition, + null).getResource(); // Check the size of included and excluded paths. Assert.assertEquals(2, collectionWithSecondaryIndex.getIndexingPolicy().getIncludedPaths().size()); IncludedPath includedPath = collectionWithSecondaryIndex.getIndexingPolicy().getIncludedPaths().iterator().next(); @@ -685,74 +721,174 @@ private static void checkDefaultPolicyPaths(IndexingPolicy indexingPolicy) { // included path should be 2 '_ts' and '/' Assert.assertEquals(2, indexingPolicy.getIncludedPaths().size()); - // check default path - Iterator pathItr = indexingPolicy.getIncludedPaths().iterator(); - IncludedPath firstPath = pathItr.next(); - Assert.assertEquals("/*", firstPath.getPath()); - Assert.assertEquals(2, firstPath.getIndexes().size()); - Iterator indexItr = firstPath.getIndexes().iterator(); - Index firstIndex = indexItr.next(); - Index secondIndex = indexItr.next(); - Assert.assertEquals(IndexKind.Hash, firstIndex.getKind()); - Assert.assertEquals(IndexKind.Range, secondIndex.getKind()); - Assert.assertEquals(DataType.String, ((HashIndex)firstIndex).getDataType()); - Assert.assertEquals((short)3, ((HashIndex)firstIndex).getPrecision()); - Assert.assertEquals(IndexKind.Range, secondIndex.getKind()); - Assert.assertEquals(DataType.Number, ((RangeIndex)secondIndex).getDataType()); - Assert.assertEquals((short)-1, ((RangeIndex)secondIndex).getPrecision()); - - // _ts - IncludedPath secondPath = pathItr.next(); - Assert.assertEquals("/\"_ts\"/?", secondPath.getPath()); + // check default path and ts path + IncludedPath rootIncludedPath = null; + IncludedPath tsIncludedPath = null; + for (IncludedPath path : indexingPolicy.getIncludedPaths()) { + if (path.getPath().equals("/*")) { + rootIncludedPath = path; + } else if (path.getPath().equals("/\"_ts\"/?")) { + tsIncludedPath = path; + } + } + // ts path should exist. + Assert.assertNotNull(tsIncludedPath); + // root path should exist. + Assert.assertNotNull(rootIncludedPath); + // RangeIndex for Numbers and HashIndex for Strings. + Assert.assertEquals(2, rootIncludedPath.getIndexes().size()); + + // There exists one HashIndex and one RangeIndex out of these 2 indexes. + HashIndex hashIndex = null; + RangeIndex rangeIndex = null; + for (Index index : rootIncludedPath.getIndexes()) { + if (index.getKind() == IndexKind.Hash) { + hashIndex = (HashIndex)index; + } else if (index.getKind() == IndexKind.Range) { + rangeIndex = (RangeIndex)index; + } + } + Assert.assertNotNull(hashIndex); + Assert.assertNotNull(rangeIndex); + + // HashIndex for Strings, skipping checking for precision as that default is set at backend and can change + Assert.assertEquals(DataType.String, hashIndex.getDataType()); + // RangeIndex for Numbers, skipping checking for precision as that default is set at backend and can change + Assert.assertEquals(DataType.Number, rangeIndex.getDataType()); + } + + @Test + public void testIndexingPolicyOverrides() throws DocumentClientException { + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + HashIndex hashIndexOverride = Index.Hash(DataType.String, 5); + RangeIndex rangeIndexOverride = Index.Range(DataType.Number, 2); + SpatialIndex spatialIndexOverride = Index.Spatial(DataType.Point); + + Index[] indexes = {hashIndexOverride, rangeIndexOverride, spatialIndexOverride}; + IndexingPolicy indexingPolicy = new IndexingPolicy(indexes); + + DocumentCollection collection = new DocumentCollection(); + collection.setId(GatewayTests.getUID()); + collection.setIndexingPolicy(indexingPolicy); + + DocumentCollection createdCollection = client.createCollection(this.databaseForTest.getSelfLink(), collection, null).getResource(); + + // check default path. + IncludedPath rootIncludedPath = null; + for (IncludedPath path : createdCollection.getIndexingPolicy().getIncludedPaths()) { + if (path.getPath().equals("/*")) { + rootIncludedPath = path; + } + } + // root path should exist. + Assert.assertNotNull(rootIncludedPath); + + Assert.assertEquals(3, rootIncludedPath.getIndexes().size()); + + HashIndex hashIndex = null; + RangeIndex rangeIndex = null; + SpatialIndex spatialIndex = null; + for (Index index : rootIncludedPath.getIndexes()) { + if (index.getKind() == IndexKind.Hash) { + hashIndex = (HashIndex)index; + } else if (index.getKind() == IndexKind.Range) { + rangeIndex = (RangeIndex)index; + } else if (index.getKind() == IndexKind.Spatial) { + spatialIndex = (SpatialIndex)index; + } + } + Assert.assertNotNull(hashIndex); + Assert.assertNotNull(rangeIndex); + Assert.assertNotNull(spatialIndex); + + Assert.assertEquals(DataType.String, hashIndex.getDataType()); + Assert.assertEquals(5, hashIndex.getPrecision()); + + Assert.assertEquals(DataType.Number, rangeIndex.getDataType()); + Assert.assertEquals(2, rangeIndex.getPrecision()); + + Assert.assertEquals(DataType.Point, spatialIndex.getDataType()); + } + + @Test + public void testIndexClassesSerialization() { + HashIndex hashIndex = new HashIndex("{\"kind\": \"Hash\", \"dataType\": \"String\", \"precision\": 8}"); + Assert.assertEquals(IndexKind.Hash, hashIndex.getKind()); + Assert.assertEquals(DataType.String, hashIndex.getDataType()); + Assert.assertEquals(8, hashIndex.getPrecision()); + + RangeIndex rangeIndex = new RangeIndex("{\"kind\": \"Range\", \"dataType\": \"Number\", \"precision\": 4}"); + Assert.assertEquals(IndexKind.Range, rangeIndex.getKind()); + Assert.assertEquals(DataType.Number, rangeIndex.getDataType()); + Assert.assertEquals(4, rangeIndex.getPrecision()); + + SpatialIndex spatialIndex = new SpatialIndex("{\"kind\": \"Spatial\", \"dataType\": \"Point\"}"); + Assert.assertEquals(IndexKind.Spatial, spatialIndex.getKind()); + Assert.assertEquals(DataType.Point, spatialIndex.getDataType()); + } + + @Test + public void testDocumentCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testDocumentCrud(isNameBased); } @Test - public void testDocumentCrud() throws DocumentClientException { + public void testDocumentCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testDocumentCrud(isNameBased); + } + + private void testDocumentCrud(boolean isNameBased) throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Read documents. - List documents = client.readDocuments(this.collectionForTest.getSelfLink(), - null).getQueryIterable().toList(); + List documents = client.readDocuments(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null).getQueryIterable().toList(); Assert.assertEquals(0, documents.size()); // create a document Document documentDefinition = new Document( "{" + - " 'name': 'sample document'," + - " 'foo': 'bar 你好'," + // foo contains some UTF-8 characters. - " 'key': 'value'" + + " 'name': 'sample document'," + + " 'foo': 'bar 你好'," + // foo contains some UTF-8 characters. + " 'key': 'value'" + "}"); // Should throw an error because automatic id generation is disabled. try { - client.createDocument(this.collectionForTest.getSelfLink(), documentDefinition, null, true); + client.createDocument(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), documentDefinition, null, true); Assert.fail("Exception didn't happen."); } catch (DocumentClientException e) { Assert.assertEquals(400, e.getStatusCode()); Assert.assertEquals("BadRequest", e.getError().getCode()); } - Document document = client.createDocument(this.collectionForTest.getSelfLink(), - documentDefinition, - null, - false).getResource(); + Document document = client.createDocument(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + documentDefinition, + null, + false).getResource(); Assert.assertEquals(documentDefinition.getString("name"), document.getString("name")); Assert.assertEquals(documentDefinition.getString("foo"), document.getString("foo")); Assert.assertEquals(documentDefinition.getString("bar"), document.getString("bar")); Assert.assertNotNull(document.getId()); // Read documents after creation. - documents = client.readDocuments(this.collectionForTest.getSelfLink(), null).getQueryIterable().toList(); + documents = client.readDocuments(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null).getQueryIterable().toList(); // Create should increase the number of documents. Assert.assertEquals(1, documents.size()); // Query documents. - documents = client.queryDocuments(this.collectionForTest.getSelfLink(), - new SqlQuerySpec("SELECT * FROM root r WHERE r.name=@id", - new SqlParameterCollection(new SqlParameter( - "@id", documentDefinition.getString("name")))), - null).getQueryIterable().toList(); + documents = client.queryDocuments(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + new SqlQuerySpec("SELECT * FROM root r WHERE r.name=@id", + new SqlParameterCollection(new SqlParameter( + "@id", documentDefinition.getString("name")))), + null).getQueryIterable().toList(); Assert.assertEquals(1, documents.size()); // Replace document. @@ -766,7 +902,7 @@ public void testDocumentCrud() throws DocumentClientException { // Document id should stay the same. Assert.assertEquals(document.getId(), replacedDocument.getId()); // Read document. - Document oneDocumentFromRead = client.readDocument(replacedDocument.getSelfLink(), null).getResource(); + Document oneDocumentFromRead = client.readDocument(this.getDocumentLink(this.databaseForTest, this.collectionForTest, replacedDocument, isNameBased), null).getResource(); Assert.assertEquals(replacedDocument.getId(), oneDocumentFromRead.getId()); AccessCondition accessCondition = new AccessCondition(); @@ -775,15 +911,15 @@ public void testDocumentCrud() throws DocumentClientException { RequestOptions options = new RequestOptions(); options.setAccessCondition(accessCondition); - ResourceResponse rr = client.readDocument(oneDocumentFromRead.getSelfLink(), options); + ResourceResponse rr = client.readDocument(this.getDocumentLink(this.databaseForTest, this.collectionForTest, oneDocumentFromRead, isNameBased), options); Assert.assertEquals(rr.getStatusCode(), HttpStatus.SC_NOT_MODIFIED); // delete document - client.deleteDocument(replacedDocument.getSelfLink(), null); + client.deleteDocument(this.getDocumentLink(this.databaseForTest, this.collectionForTest, replacedDocument, isNameBased), null); // read documents after deletion try { - client.readDocument(replacedDocument.getSelfLink(), null); + client.readDocument(this.getDocumentLink(this.databaseForTest, this.collectionForTest, replacedDocument, isNameBased), null); Assert.fail("Exception didn't happen."); } catch (DocumentClientException e) { Assert.assertEquals(404, e.getStatusCode()); @@ -791,6 +927,112 @@ public void testDocumentCrud() throws DocumentClientException { } } + // Upsert test for Document resource - self link version + @Test + public void testDocumentUpsert_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testDocumentUpsert(isNameBased); + } + + // Upsert test for Document resource - name based routing version + @Test + public void testDocumentUpsert_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testDocumentUpsert(isNameBased); + } + + private void testDocumentUpsert(boolean isNameBased) throws DocumentClientException { + // Create document client + DocumentClient client = new DocumentClient(HOST, MASTER_KEY, ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Read documents and get initial count + List documents = client + .readDocuments( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + int initialDocumentCount = documents.size(); + + // Create a document definition + Document documentDefinition = new Document( + "{" + + " 'name': 'sample document'," + + " 'key': 'value'" + + "}"); + + // Upsert should create the document with the definition + Document createdDocument = client.upsertDocument( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + documentDefinition, null, false).getResource(); + + // Read documents to check the count and it should increase + documents = client + .readDocuments( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialDocumentCount + 1, documents.size()); + + // Update document. + createdDocument.set("name", "replaced document"); + createdDocument.set("key", "new value"); + + // Upsert should replace the existing document since Id exists + Document upsertedDocument = client.upsertDocument( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), createdDocument, + null, true).getResource(); + + // Document id should stay the same. + Assert.assertEquals(createdDocument.getId(), upsertedDocument.getId()); + + // Property should have changed. + Assert.assertEquals(createdDocument.getString("name"), upsertedDocument.getString("name")); + Assert.assertEquals(createdDocument.getString("key"), upsertedDocument.getString("key")); + + // Documents count should remain the same + documents = client + .readDocuments( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialDocumentCount + 1, documents.size()); + + // Update document id + createdDocument.setId(GatewayTests.getUID()); + + // Upsert should create new document + Document newDocument = client.upsertDocument( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), createdDocument, + null, true).getResource(); + + // Verify id property + Assert.assertEquals(createdDocument.getId(), newDocument.getId()); + + // Read documents after upsert to check the count and it should increase + documents = client + .readDocuments( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialDocumentCount + 2, documents.size()); + + // Delete documents + client.deleteDocument( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, upsertedDocument, isNameBased), + null); + client.deleteDocument( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, newDocument, isNameBased), null); + + // Read documents after delete to check the count and it should be back to original + documents = client + .readDocuments( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialDocumentCount, documents.size()); + } + class TestPOJOInner { public int intProperty; @@ -834,9 +1076,9 @@ public void testPOJODocumentCrud() throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); TestPOJO testPojo = new TestPOJO(10); testPojo.id= "MyPojoObejct" + GatewayTests.getUID(); @@ -845,9 +1087,9 @@ public void testPOJODocumentCrud() throws DocumentClientException { testPojo.setPrivateStringProperty("testStringAccess"); Document document = client.createDocument(this.collectionForTest.getSelfLink(), - testPojo, - null, - false).getResource(); + testPojo, + null, + false).getResource(); Assert.assertEquals(document.getInt("intProperty").intValue(), testPojo.intProperty); @@ -875,7 +1117,18 @@ public void testPOJODocumentCrud() throws DocumentClientException { } @Test - public void testAttachmentCrud() throws DocumentClientException { + public void testAttachmentCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testAttachmentCrud(isNameBased); + } + + @Test + public void testAttachmentCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testAttachmentCrud(isNameBased); + } + + private void testAttachmentCrud(boolean isNameBased) throws DocumentClientException { class ReadableStream extends InputStream { byte[] bytes; @@ -896,23 +1149,23 @@ public int read() throws IOException { } DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Create document. Document documentDefinition = new Document( "{" + - " 'id': 'sample document'," + - " 'foo': 'bar'," + - " 'key': 'value'" + + " 'id': 'sample document'," + + " 'foo': 'bar'," + + " 'key': 'value'" + "}"); - Document document = client.createDocument(this.collectionForTest.getSelfLink(), - documentDefinition, - null, - false).getResource(); + Document document = client.createDocument(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + documentDefinition, + null, + false).getResource(); // List all attachments. - List attachments = client.readAttachments(document.getSelfLink(), - null).getQueryIterable().toList(); + List attachments = client.readAttachments(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), + null).getQueryIterable().toList(); Assert.assertEquals(0, attachments.size()); MediaOptions validMediaOptions = new MediaOptions(); @@ -925,7 +1178,7 @@ public int read() throws IOException { // Create attachment with invalid content type. ReadableStream mediaStream = new ReadableStream("stream content."); try { - client.createAttachment(document.getSelfLink(), mediaStream, invalidMediaOptions); + client.createAttachment(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), mediaStream, invalidMediaOptions); Assert.assertTrue(false); // This line shouldn't execute. } catch (DocumentClientException e) { Assert.assertEquals(400, e.getStatusCode()); @@ -934,15 +1187,15 @@ public int read() throws IOException { // Create attachment with valid content type. mediaStream = new ReadableStream("stream content."); - Attachment validAttachment = client.createAttachment(document.getSelfLink(), - mediaStream, - validMediaOptions).getResource(); + Attachment validAttachment = client.createAttachment(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), + mediaStream, + validMediaOptions).getResource(); Assert.assertEquals("attachment id", validAttachment.getId()); mediaStream = new ReadableStream("stream content"); // Create colliding attachment. try { - client.createAttachment(document.getSelfLink(), mediaStream, validMediaOptions); + client.createAttachment(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), mediaStream, validMediaOptions); Assert.fail("Exception didn't happen."); } catch (DocumentClientException e) { Assert.assertEquals(409, e.getStatusCode()); @@ -952,23 +1205,23 @@ public int read() throws IOException { // Create attachment with media link. Attachment attachmentDefinition = new Attachment( "{" + - " 'id': 'dynamic attachment'," + - " 'media': 'http://xstore.'," + - " 'MediaType': 'Book'," + - " 'Author': 'My Book Author'," + - " 'Title': 'My Book Title'," + - " 'contentType': 'application/text'" + + " 'id': 'dynamic attachment'," + + " 'media': 'http://xstore.'," + + " 'MediaType': 'Book'," + + " 'Author': 'My Book Author'," + + " 'Title': 'My Book Title'," + + " 'contentType': 'application/text'" + "}"); - Attachment attachment = client.createAttachment(document.getSelfLink(), - attachmentDefinition, - null).getResource(); + Attachment attachment = client.createAttachment(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), + attachmentDefinition, + null).getResource(); Assert.assertEquals("Book", attachment.getString("MediaType")); Assert.assertEquals("My Book Author", attachment.getString("Author")); // List all attachments. FeedOptions fo = new FeedOptions(); fo.setPageSize(1); - attachments = client.readAttachments(document.getSelfLink(), fo).getQueryIterable().toList(); + attachments = client.readAttachments(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), fo).getQueryIterable().toList(); Assert.assertEquals(2, attachments.size()); attachment.set("Author", "new author"); @@ -998,36 +1251,239 @@ public int read() throws IOException { // Share attachment with a second document. documentDefinition = new Document("{'id': 'document 2'}"); - document = client.createDocument(this.collectionForTest.getSelfLink(), - documentDefinition, - null, - false).getResource(); + document = client.createDocument(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + documentDefinition, + null, + false).getResource(); String secondAttachmentJson = String.format("{" + - " 'id': '%s'," + - " 'contentType': '%s'," + - " 'media': '%s'," + - " }", - validAttachment.getId(), - validAttachment.getContentType(), - validAttachment.getMediaLink()); + " 'id': '%s'," + + " 'contentType': '%s'," + + " 'media': '%s'," + + " }", + validAttachment.getId(), + validAttachment.getContentType(), + validAttachment.getMediaLink()); Attachment secondAttachmentDefinition = new Attachment(secondAttachmentJson); - attachment = client.createAttachment(document.getSelfLink(), secondAttachmentDefinition, null).getResource(); + attachment = client.createAttachment(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), secondAttachmentDefinition, null).getResource(); Assert.assertEquals(validAttachment.getId(), attachment.getId()); Assert.assertEquals(validAttachment.getMediaLink(), attachment.getMediaLink()); Assert.assertEquals(validAttachment.getContentType(), attachment.getContentType()); // Deleting attachment. - client.deleteAttachment(attachment.getSelfLink(), null); + client.deleteAttachment(this.getAttachmentLink(this.databaseForTest, this.collectionForTest, document, attachment, isNameBased), null); // read attachments after deletion - attachments = client.readAttachments(document.getSelfLink(), null).getQueryIterable().toList(); + attachments = client.readAttachments(this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), null).getQueryIterable().toList(); Assert.assertEquals(0, attachments.size()); } + // Upsert test for Attachment resource - self link version + @Test + public void testAttachmentUpsert_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testAttachmentUpsert(isNameBased); + } + + // Upsert test for Attachment resource - name based routing version + @Test + public void testAttachmentUpsert_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testAttachmentUpsert(isNameBased); + } + + private void testAttachmentUpsert(boolean isNameBased) throws DocumentClientException { + // Implement a readable stream class + class ReadableStream extends InputStream { + + byte[] bytes; + int index; + + ReadableStream(String content) { + this.bytes = content.getBytes(); + this.index = 0; + } + + @Override + public int read() throws IOException { + if (this.index < this.bytes.length) { + return this.bytes[this.index++]; + } + return -1; + } + } + + // Create document client + DocumentClient client = new DocumentClient(HOST, MASTER_KEY, ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Create document definition + Document documentDefinition = new Document( + "{" + + " 'name': 'document'," + + " 'key': 'value'" + + "}"); + + // Create document + Document document = client.createDocument( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + documentDefinition, null, false).getResource(); + + // Read attachments and get initial count + List attachments = client + .readAttachments( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), null) + .getQueryIterable().toList(); + + int initialAttachmentCount = attachments.size(); + + // Set MediaOptions + MediaOptions mediaOptions = new MediaOptions(); + mediaOptions.setSlug("attachment id"); + mediaOptions.setContentType("application/text"); + + // Set Feed Options + FeedOptions fo = new FeedOptions(); + fo.setPageSize(1); + + // Initialize media stream + ReadableStream mediaStream = new ReadableStream("stream content."); + + // Upsert should create the attachment + Attachment createdMediaStreamAttachment = client.upsertAttachment( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), mediaStream, + mediaOptions).getResource(); + + Assert.assertEquals("attachment id", createdMediaStreamAttachment.getId()); + + // Read attachments to check the count and it should increase + attachments = client + .readAttachments( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), fo) + .getQueryIterable().toList(); + + Assert.assertEquals(initialAttachmentCount + 1, attachments.size()); + + // Update MediaOptions + mediaOptions.setSlug("new attachment id"); + mediaStream = new ReadableStream("stream content."); + + // Upsert should create new attachment + Attachment newMediaStreamAttachment = client.upsertAttachment( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), mediaStream, + mediaOptions).getResource(); + + Assert.assertEquals("new attachment id", newMediaStreamAttachment.getId()); + + // Read attachments to check the count and it should increase + attachments = client + .readAttachments( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), fo) + .getQueryIterable().toList(); + + Assert.assertEquals(initialAttachmentCount + 2, attachments.size()); + + // Create attachment definition with media link. + Attachment attachmentDefinition = new Attachment( + "{" + + " 'id': 'dynamic attachment'," + + " 'media': 'http://xstore.'," + + " 'MediaType': 'Book'," + + " 'Author': 'My Book Author'," + + " 'Title': 'My Book Title'," + + " 'contentType': 'application/text'" + + "}"); + + // Upsert should create the attachment + Attachment createdDynamicAttachment = client.upsertAttachment( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), + attachmentDefinition, null).getResource(); + + // Verify id property + Assert.assertEquals(attachmentDefinition.getId(), createdDynamicAttachment.getId()); + + // Read all attachments and verify the count increase + attachments = client + .readAttachments( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), fo) + .getQueryIterable().toList(); + + Assert.assertEquals(initialAttachmentCount + 3, attachments.size()); + + // Update attachment + createdDynamicAttachment.set("Author", "new author"); + + // Upsert should replace the existing attachment since Id exists + Attachment upsertedDynamicAttachment = client.upsertAttachment( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), + createdDynamicAttachment, null).getResource(); + + // Verify id property + Assert.assertEquals(createdDynamicAttachment.getId(), upsertedDynamicAttachment.getId()); + + // Verify property change + Assert.assertEquals(createdDynamicAttachment.getString("Author"), upsertedDynamicAttachment.getString("Author")); + + // Read all attachments and verify the count remains the same + attachments = client + .readAttachments( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), fo) + .getQueryIterable().toList(); + + Assert.assertEquals(initialAttachmentCount + 3, attachments.size()); + + // Change id property + createdDynamicAttachment.setId(GatewayTests.getUID()); + + // Upsert should create new attachment + Attachment newDynamicAttachment = client.upsertAttachment( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), + createdDynamicAttachment, null).getResource(); + + // Verify id property + Assert.assertEquals(createdDynamicAttachment.getId(), newDynamicAttachment.getId()); + + // Read all attachments and verify the count increases + attachments = client + .readAttachments( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), fo) + .getQueryIterable().toList(); + + Assert.assertEquals(initialAttachmentCount + 4, attachments.size()); + + // Deleting attachments. + client.deleteAttachment(this.getAttachmentLink(this.databaseForTest, this.collectionForTest, document, + createdMediaStreamAttachment, isNameBased), null); + client.deleteAttachment(this.getAttachmentLink(this.databaseForTest, this.collectionForTest, document, + newMediaStreamAttachment, isNameBased), null); + client.deleteAttachment(this.getAttachmentLink(this.databaseForTest, this.collectionForTest, document, + upsertedDynamicAttachment, isNameBased), null); + client.deleteAttachment(this.getAttachmentLink(this.databaseForTest, this.collectionForTest, document, + newDynamicAttachment, isNameBased), null); + + // read attachments after deletion and verify count remains the same + attachments = client + .readAttachments( + this.getDocumentLink(this.databaseForTest, this.collectionForTest, document, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialAttachmentCount, attachments.size()); + } + + @Test + public void testTriggerCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testTriggerCrud(isNameBased); + } + @Test - public void testTriggerCrud() throws DocumentClientException { + public void testTriggerCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testTriggerCrud(isNameBased); + } + + private void testTriggerCrud(boolean isNameBased) throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // create.. Trigger triggerDef = new Trigger(); @@ -1035,9 +1491,9 @@ public void testTriggerCrud() throws DocumentClientException { triggerDef.setTriggerType(TriggerType.Pre); triggerDef.setTriggerOperation(TriggerOperation.All); triggerDef.setBody("function() {var x = 10;}"); - Trigger newTrigger = client.createTrigger(this.collectionForTest.getSelfLink(), - triggerDef, - null).getResource(); + Trigger newTrigger = client.createTrigger(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + triggerDef, + null).getResource(); Assert.assertNotNull(newTrigger.getBody()); Assert.assertNotNull(newTrigger.getETag()); @@ -1048,7 +1504,7 @@ public void testTriggerCrud() throws DocumentClientException { Document document = new Document(); document.setId("noname"); - client.createDocument(this.collectionForTest.getSelfLink(), document, options, false); + client.createDocument(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), document, options, false); // replace... String id = GatewayTests.getUID(); @@ -1057,46 +1513,161 @@ public void testTriggerCrud() throws DocumentClientException { newTrigger = client.replaceTrigger(newTrigger, null).getResource(); Assert.assertEquals(newTrigger.getId(), id); - newTrigger = client.readTrigger(newTrigger.getSelfLink(), null).getResource(); + newTrigger = client.readTrigger(this.getTriggerLink(this.databaseForTest, this.collectionForTest, newTrigger, isNameBased), null).getResource(); // read triggers: - List triggers = client.readTriggers(this.collectionForTest.getSelfLink(), - null).getQueryIterable().toList(); + List triggers = client.readTriggers(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null).getQueryIterable().toList(); if (triggers.size() > 0) { // } else { Assert.fail("Readfeeds fail to find trigger"); } - triggers = client.queryTriggers(this.collectionForTest.getSelfLink(), - new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection( - new SqlParameter("@id", newTrigger.getId()))), - null).getQueryIterable().toList(); + triggers = client.queryTriggers(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", + new SqlParameterCollection( + new SqlParameter("@id", newTrigger.getId()))), + null).getQueryIterable().toList(); if (triggers.size() > 0) { // } else { Assert.fail("Query fail to find trigger"); } - client.deleteTrigger(newTrigger.getSelfLink(), null); + client.deleteTrigger(this.getTriggerLink(this.databaseForTest, this.collectionForTest, newTrigger, isNameBased), null); + } + + // Upsert test for Trigger resource - self link version + @Test + public void testTriggerUpsert_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testTriggerUpsert(isNameBased); + } + + // Upsert test for Trigger resource - name based routing version + @Test + public void testTriggerUpsert_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testTriggerUpsert(isNameBased); + } + + private void testTriggerUpsert(boolean isNameBased) throws DocumentClientException { + // Create document client + DocumentClient client = new DocumentClient(HOST, MASTER_KEY, ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Read triggers and get initial count + List triggers = client + .readTriggers(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + int initialTriggerCount = triggers.size(); + + // Create trigger definition + Trigger triggerDefinition = new Trigger(); + triggerDefinition.setId(GatewayTests.getUID()); + triggerDefinition.setTriggerType(TriggerType.Pre); + triggerDefinition.setTriggerOperation(TriggerOperation.All); + triggerDefinition.setBody("function() {var x = 10;}"); + + // Upsert should create the trigger + Trigger createdTrigger = client.upsertTrigger( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + triggerDefinition, null).getResource(); + + // Verify Id property + Assert.assertEquals(triggerDefinition.getId(), createdTrigger.getId()); + + // Read triggers to check the count and it should increase + triggers = client.readTriggers(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialTriggerCount + 1, triggers.size()); + + // Update trigger + createdTrigger.setTriggerOperation(TriggerOperation.Update); + createdTrigger.setBody("function() {var x = 20;}"); + + // Upsert should replace the trigger since it already exists + Trigger upsertedTrigger = client.upsertTrigger( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + createdTrigger, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdTrigger.getId(), upsertedTrigger.getId()); + + // Verify updated property + Assert.assertEquals(createdTrigger.getTriggerOperation(), upsertedTrigger.getTriggerOperation()); + Assert.assertEquals(createdTrigger.getBody(), upsertedTrigger.getBody()); + + // Read triggers to check the count and it should remain the same + triggers = client.readTriggers(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialTriggerCount + 1, triggers.size()); + + // Update trigger id + createdTrigger.setId(GatewayTests.getUID()); + + // Upsert should create new trigger since id is changed + Trigger newTrigger = client.upsertTrigger( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + createdTrigger, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdTrigger.getId(), newTrigger.getId()); + + // Read triggers to check the count and it should increase + triggers = client.readTriggers(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialTriggerCount + 2, triggers.size()); + + // Delete triggers + client.deleteTrigger(this.getTriggerLink(this.databaseForTest, this.collectionForTest, upsertedTrigger, isNameBased), + null); + client.deleteTrigger(this.getTriggerLink(this.databaseForTest, this.collectionForTest, newTrigger, isNameBased), + null); + + // Read triggers to check the count and it should remain same + triggers = client.readTriggers(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialTriggerCount, triggers.size()); + } + + @Test + public void testStoredProcedureCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testStoredProcedureCrud(isNameBased); } @Test - public void testStoredProcedureCrud() throws DocumentClientException { + public void testStoredProcedureCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testStoredProcedureCrud(isNameBased); + } + + private void testStoredProcedureCrud(boolean isNameBased) throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // create.. StoredProcedure storedProcedureDef = new StoredProcedure(); storedProcedureDef.setId(GatewayTests.getUID()); storedProcedureDef.setBody("function() {var x = 10;}"); - StoredProcedure newStoredProcedure = client.createStoredProcedure(this.collectionForTest.getSelfLink(), - storedProcedureDef, - null).getResource(); + StoredProcedure newStoredProcedure = client.createStoredProcedure(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + storedProcedureDef, + null).getResource(); Assert.assertNotNull(newStoredProcedure.getBody()); Assert.assertNotNull(newStoredProcedure.getETag()); @@ -1107,88 +1678,203 @@ public void testStoredProcedureCrud() throws DocumentClientException { newStoredProcedure = client.replaceStoredProcedure(newStoredProcedure, null).getResource(); Assert.assertEquals(newStoredProcedure.getId(), id); - newStoredProcedure = client.readStoredProcedure(newStoredProcedure.getSelfLink(), null).getResource(); + newStoredProcedure = client.readStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, newStoredProcedure, isNameBased), null).getResource(); // read storedProcedures: - List storedProcedures = client.readStoredProcedures(this.collectionForTest.getSelfLink(), - null).getQueryIterable().toList(); + List storedProcedures = client.readStoredProcedures(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null).getQueryIterable().toList(); if (storedProcedures.size() > 0) { // } else { Assert.fail("Readfeeds fail to find StoredProcedure"); } - storedProcedures = client.queryStoredProcedures(this.collectionForTest.getSelfLink(), - new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter( - "@id", newStoredProcedure.getId()))), - null).getQueryIterable().toList(); + storedProcedures = client.queryStoredProcedures(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", + new SqlParameterCollection(new SqlParameter( + "@id", newStoredProcedure.getId()))), + null).getQueryIterable().toList(); if (storedProcedures.size() > 0) { // } else { Assert.fail("Query fail to find StoredProcedure"); } - client.deleteStoredProcedure(newStoredProcedure.getSelfLink(), null); + client.deleteStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, newStoredProcedure, isNameBased), null); + } + + // Upsert test for StoredProcedure resource - self link version + @Test + public void testStoredProcedureUpsert_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testStoredProcedureUpsert(isNameBased); } + // Upsert test for StoredProcedure resource - name based routing version @Test - public void testStoredProcedureFunctionality() - throws DocumentClientException { - DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + public void testStoredProcedureUpsert_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testStoredProcedureUpsert(isNameBased); + } - StoredProcedure sproc1 = new StoredProcedure( - "{" + - " 'id': 'storedProcedure1'," + - " 'body':" + - " 'function () {" + - " for (var i = 0; i < 1000; i++) {" + - " var item = getContext().getResponse().getBody();" + - " if (i > 0 && item != i - 1) throw \"body mismatch\";" + - " getContext().getResponse().setBody(i);" + - " }" + - " }'" + + private void testStoredProcedureUpsert(boolean isNameBased) throws DocumentClientException { + // Create document client + DocumentClient client = new DocumentClient(HOST, MASTER_KEY, ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Read sprocs and get initial count + List sprocs = client + .readStoredProcedures(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + int initialStoredProcedureCount = sprocs.size(); + + // Create stored procedure definition + StoredProcedure storedProcedureDefinition = new StoredProcedure(); + storedProcedureDefinition.setId(GatewayTests.getUID()); + storedProcedureDefinition.setBody("function() {var x = 10;}"); + + // Upsert should create the sproc + StoredProcedure createdStoredProcedure = client.upsertStoredProcedure( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + storedProcedureDefinition, null).getResource(); + + // Verify Id property + Assert.assertEquals(storedProcedureDefinition.getId(), createdStoredProcedure.getId()); + + // Read sprocs to check the count and it should increase + sprocs = client + .readStoredProcedures( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialStoredProcedureCount + 1, sprocs.size()); + + // Update sproc + createdStoredProcedure.setBody("function() {var x = 20;}"); + + // Upsert should replace the sproc + StoredProcedure upsertedStoredProcedure = client.upsertStoredProcedure( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + createdStoredProcedure, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdStoredProcedure.getId(), upsertedStoredProcedure.getId()); + + // Verify updated property + Assert.assertEquals(createdStoredProcedure.getBody(), upsertedStoredProcedure.getBody()); + + // Read the sprocs and the count should remain the same + sprocs = client + .readStoredProcedures( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialStoredProcedureCount + 1, sprocs.size()); + + // Update sproc id + createdStoredProcedure.setId(GatewayTests.getUID()); + + // Upsert should create the sproc since id is changed + StoredProcedure newStoredProcedure = client.upsertStoredProcedure( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + createdStoredProcedure, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdStoredProcedure.getId(), newStoredProcedure.getId()); + + // Read the sprocs and the count should increase + sprocs = client + .readStoredProcedures( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialStoredProcedureCount + 2, sprocs.size()); + + // Delete sprocs + client.deleteStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, + upsertedStoredProcedure, isNameBased), null); + client.deleteStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, + newStoredProcedure, isNameBased), null); + + // Read the sprocs and the count should remain the same + sprocs = client + .readStoredProcedures( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialStoredProcedureCount, sprocs.size()); + } + + @Test + public void testStoredProcedureFunctionality_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testStoredProcedureFunctionality(isNameBased); + } + + @Test + public void testStoredProcedureFunctionality_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testStoredProcedureFunctionality(isNameBased); + } + + private void testStoredProcedureFunctionality(boolean isNameBased) + throws DocumentClientException { + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + StoredProcedure sproc1 = new StoredProcedure( + "{" + + " 'id': 'storedProcedure1'," + + " 'body':" + + " 'function () {" + + " for (var i = 0; i < 1000; i++) {" + + " var item = getContext().getResponse().getBody();" + + " if (i > 0 && item != i - 1) throw \"body mismatch\";" + + " getContext().getResponse().setBody(i);" + + " }" + + " }'" + "}"); - StoredProcedure retrievedSproc = client.createStoredProcedure(collectionForTest.getSelfLink(), - sproc1, - null).getResource(); - String result = client.executeStoredProcedure(retrievedSproc.getSelfLink(), null).getResponseAsString(); + StoredProcedure retrievedSproc = client.createStoredProcedure(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + sproc1, + null).getResource(); + String result = client.executeStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, retrievedSproc, isNameBased), null).getResponseAsString(); Assert.assertEquals("999", result); StoredProcedure sproc2 = new StoredProcedure( "{" + - " 'id': 'storedProcedure2'," + - " 'body':" + - " 'function () {" + - " for (var i = 0; i < 10; i++) {" + - " getContext().getResponse().appendValue(\"Body\", i);" + - " }" + - " }'" + + " 'id': 'storedProcedure2'," + + " 'body':" + + " 'function () {" + + " for (var i = 0; i < 10; i++) {" + + " getContext().getResponse().appendValue(\"Body\", i);" + + " }" + + " }'" + "}"); - StoredProcedure retrievedSproc2 = client.createStoredProcedure(collectionForTest.getSelfLink(), - sproc2, - null).getResource(); - result = client.executeStoredProcedure(retrievedSproc2.getSelfLink(), null).getResponseAsString(); + StoredProcedure retrievedSproc2 = client.createStoredProcedure(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + sproc2, + null).getResource(); + result = client.executeStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, retrievedSproc2, isNameBased), null).getResponseAsString(); Assert.assertEquals("\"0123456789\"", result); // String and Integer StoredProcedure sproc3 = new StoredProcedure( "{" + - " 'id': 'storedProcedure3'," + - " 'body':" + - " 'function (value, num) {" + - " getContext().getResponse().setBody(" + - " \"a\" + value + num * 2);" + - " }'" + + " 'id': 'storedProcedure3'," + + " 'body':" + + " 'function (value, num) {" + + " getContext().getResponse().setBody(" + + " \"a\" + value + num * 2);" + + " }'" + "}"); - StoredProcedure retrievedSproc3 = client.createStoredProcedure(collectionForTest.getSelfLink(), - sproc3, - null).getResource(); - result = client.executeStoredProcedure(retrievedSproc3.getSelfLink(), - new Object[] {"so", 123}).getResponseAsString(); + StoredProcedure retrievedSproc3 = client.createStoredProcedure(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + sproc3, + null).getResource(); + result = client.executeStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, retrievedSproc3, isNameBased), + new Object[] {"so", 123}).getResponseAsString(); Assert.assertEquals("\"aso246\"", result); // POJO @@ -1199,47 +1885,58 @@ class TempPOJO { TempPOJO tempPOJO = new TempPOJO(); StoredProcedure sproc4 = new StoredProcedure( "{" + - " 'id': 'storedProcedure4'," + - " 'body':" + - " 'function (value) {" + - " getContext().getResponse().setBody(" + - " \"a\" + value.temp);" + - " }'" + + " 'id': 'storedProcedure4'," + + " 'body':" + + " 'function (value) {" + + " getContext().getResponse().setBody(" + + " \"a\" + value.temp);" + + " }'" + "}"); - StoredProcedure retrievedSproc4 = client.createStoredProcedure(collectionForTest.getSelfLink(), - sproc4, - null).getResource(); - result = client.executeStoredProcedure(retrievedSproc4.getSelfLink(), - new Object[] {tempPOJO}).getResponseAsString(); + StoredProcedure retrievedSproc4 = client.createStoredProcedure(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + sproc4, + null).getResource(); + result = client.executeStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, retrievedSproc4, isNameBased), + new Object[] {tempPOJO}).getResponseAsString(); Assert.assertEquals("\"aso2\"", result); // JSONObject JSONObject jsonObject = new JSONObject("{'temp': 'so3'}"); - result = client.executeStoredProcedure(retrievedSproc4.getSelfLink(), - new Object[] {jsonObject}).getResponseAsString(); + result = client.executeStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, retrievedSproc4, isNameBased), + new Object[] {jsonObject}).getResponseAsString(); Assert.assertEquals("\"aso3\"", result); // Document Document document = new Document("{'temp': 'so4'}"); - result = client.executeStoredProcedure(retrievedSproc4.getSelfLink(), - new Object[] {document}).getResponseAsString(); + result = client.executeStoredProcedure(this.getStoredProcedureLink(this.databaseForTest, this.collectionForTest, retrievedSproc4, isNameBased), + new Object[] {document}).getResponseAsString(); Assert.assertEquals("\"aso4\"", result); } @Test - public void testUserDefinedFunctionCrud() throws DocumentClientException { + public void testUserDefinedFunctionCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testUserDefinedFunctionCrud(isNameBased); + } + + @Test + public void testUserDefinedFunctionCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testUserDefinedFunctionCrud(isNameBased); + } + + private void testUserDefinedFunctionCrud(boolean isNameBased) throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // create.. UserDefinedFunction udfDef = new UserDefinedFunction(); udfDef.setId(GatewayTests.getUID()); udfDef.setBody("function() {var x = 10;}"); - UserDefinedFunction newUdf = client.createUserDefinedFunction(this.collectionForTest.getSelfLink(), - udfDef, - null).getResource(); + UserDefinedFunction newUdf = client.createUserDefinedFunction(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + udfDef, + null).getResource(); Assert.assertNotNull(newUdf.getBody()); Assert.assertNotNull(newUdf.getETag()); @@ -1250,13 +1947,13 @@ public void testUserDefinedFunctionCrud() throws DocumentClientException { newUdf = client.replaceUserDefinedFunction(newUdf, null).getResource(); Assert.assertEquals(newUdf.getId(), id); - newUdf = client.readUserDefinedFunction(newUdf.getSelfLink(), null).getResource(); + newUdf = client.readUserDefinedFunction(this.getUserDefinedFunctionLink(this.databaseForTest, this.collectionForTest, newUdf, isNameBased), null).getResource(); Assert.assertEquals(newUdf.getId(), id); // read udf feed: { - List udfs = client.readUserDefinedFunctions(this.collectionForTest.getSelfLink(), - null).getQueryIterable().toList(); + List udfs = client.readUserDefinedFunctions(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null).getQueryIterable().toList(); if (udfs.size() > 0) { // } else { @@ -1265,9 +1962,9 @@ public void testUserDefinedFunctionCrud() throws DocumentClientException { } { List udfs = client.queryUserDefinedFunctions( - this.collectionForTest.getSelfLink(), + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter("@id", newUdf.getId()))), + new SqlParameterCollection(new SqlParameter("@id", newUdf.getId()))), null).getQueryIterable().toList(); if (udfs.size() > 0) { // @@ -1275,32 +1972,141 @@ public void testUserDefinedFunctionCrud() throws DocumentClientException { Assert.fail("Query fail to find UserDefinedFunction"); } } - client.deleteUserDefinedFunction(newUdf.getSelfLink(), null); + client.deleteUserDefinedFunction(this.getUserDefinedFunctionLink(this.databaseForTest, this.collectionForTest, newUdf, isNameBased), null); + } + + // Upsert test for UserDefinedFunction resource - self link version + @Test + public void testUserDefinedFunctionUpsert_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testUserDefinedFunctionUpsert(isNameBased); } + // Upsert test for UserDefinedFunction resource - name based routing version @Test - public void testUserCrud() throws DocumentClientException { + public void testUserDefinedFunctionUpsert_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testUserDefinedFunctionUpsert(isNameBased); + } + + private void testUserDefinedFunctionUpsert(boolean isNameBased) throws DocumentClientException { + // Create document client DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Read user defined functions and get initial count + List udfs = client.readUserDefinedFunctions(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null).getQueryIterable().toList(); + + int initialUserDefinedFunctionCount = udfs.size(); + + // Create user defined function definition + UserDefinedFunction udfDefinition = new UserDefinedFunction(); + udfDefinition.setId(GatewayTests.getUID()); + udfDefinition.setBody("function() {var x = 10;}"); + + // Upsert should create the udf + UserDefinedFunction createdUserDefinedFunction = client.upsertUserDefinedFunction( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + udfDefinition, null).getResource(); + + // Verify Id property + Assert.assertEquals(udfDefinition.getId(), createdUserDefinedFunction.getId()); + + // Read udfs to check the count and it should increase + udfs = client.readUserDefinedFunctions(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialUserDefinedFunctionCount + 1, udfs.size()); + + // Update udf + createdUserDefinedFunction.setBody("function() {var x = 20;}"); + + // Upsert should replace the trigger since it already exists + UserDefinedFunction upsertedUserDefinedFunction = client.upsertUserDefinedFunction( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + createdUserDefinedFunction, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdUserDefinedFunction.getId(), upsertedUserDefinedFunction.getId()); + + // Verify updated property + Assert.assertEquals(createdUserDefinedFunction.getBody(), upsertedUserDefinedFunction.getBody()); + + // Read udfs to check the count and it should remain the same + udfs = client.readUserDefinedFunctions(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialUserDefinedFunctionCount + 1, udfs.size()); + + // Update udf id + createdUserDefinedFunction.setId(GatewayTests.getUID()); + + // Upsert should create new udf since id is changed + UserDefinedFunction newUserDefinedFunction = client.upsertUserDefinedFunction( + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + createdUserDefinedFunction, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdUserDefinedFunction.getId(), newUserDefinedFunction.getId()); + + // Read udfs to check the count and it should increase + udfs = client.readUserDefinedFunctions(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialUserDefinedFunctionCount + 2, udfs.size()); + + // Delete udfs + client.deleteUserDefinedFunction(this.getUserDefinedFunctionLink(this.databaseForTest, this.collectionForTest, upsertedUserDefinedFunction, isNameBased), null); + client.deleteUserDefinedFunction(this.getUserDefinedFunctionLink(this.databaseForTest, this.collectionForTest, newUserDefinedFunction, isNameBased), null); + + // Read udfs to check the count and it should remain same + udfs = client.readUserDefinedFunctions(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), + null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialUserDefinedFunctionCount, udfs.size()); + } + + @Test + public void testUserCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testUserCrud(isNameBased); + } + + @Test + public void testUserCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testUserCrud(isNameBased); + } + + private void testUserCrud(boolean isNameBased) throws DocumentClientException { + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // List users. - List users = client.readUsers(databaseForTest.getSelfLink(), null).getQueryIterable().toList(); + List users = client.readUsers(this.getDatabaseLink(this.databaseForTest, isNameBased), null).getQueryIterable().toList(); int beforeCreateCount = users.size(); // Create user. - User user = client.createUser(databaseForTest.getSelfLink(), - new User("{ 'id': 'new user' }"), - null).getResource(); + User user = client.createUser(this.getDatabaseLink(this.databaseForTest, isNameBased), + new User("{ 'id': 'new user' }"), + null).getResource(); Assert.assertEquals("new user", user.getId()); // List users after creation. - users = client.readUsers(databaseForTest.getSelfLink(), null).getQueryIterable().toList(); + users = client.readUsers(this.getDatabaseLink(this.databaseForTest, isNameBased), null).getQueryIterable().toList(); Assert.assertEquals(beforeCreateCount + 1, users.size()); // Query users. - users = client.queryUsers(databaseForTest.getSelfLink(), - new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter("@id", "new user"))), - null).getQueryIterable().toList(); + users = client.queryUsers(this.getDatabaseLink(this.databaseForTest, isNameBased), + new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", + new SqlParameterCollection(new SqlParameter("@id", "new user"))), + null).getQueryIterable().toList(); Assert.assertEquals(1, users.size()); // Replace user. @@ -1310,33 +2116,118 @@ public void testUserCrud() throws DocumentClientException { Assert.assertEquals(user.getId(), replacedUser.getId()); // Read user. - user = client.readUser(replacedUser.getSelfLink(), null).getResource(); + user = client.readUser(this.getUserLink(this.databaseForTest, replacedUser, isNameBased), null).getResource(); Assert.assertEquals(replacedUser.getId(), user.getId()); // Delete user. - client.deleteUser(user.getSelfLink(), null); + client.deleteUser(this.getUserLink(this.databaseForTest, user, isNameBased), null); // Read user after deletion. try { - client.readUser(user.getSelfLink(), null); + client.readUser(this.getUserLink(this.databaseForTest, replacedUser, isNameBased), null); Assert.fail("Exception didn't happen."); } catch (DocumentClientException e) { Assert.assertEquals(404, e.getStatusCode()); Assert.assertEquals("NotFound", e.getError().getCode()); } } + + // Upsert test for User resource - self link version + @Test + public void testUserUpsert_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testUserUpsert(isNameBased); + } + // Upsert test for User resource - name based routing version @Test - public void testPermissionCrud() throws DocumentClientException { + public void testUserUpsert_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testUserUpsert(isNameBased); + } + + private void testUserUpsert(boolean isNameBased) throws DocumentClientException { + // Create document client DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Read users and get initial count + List users = client + .readUsers(this.getDatabaseLink(this.databaseForTest, isNameBased), null) + .getQueryIterable().toList(); + + int initialUserCount = users.size(); + + // Create user definition + User userDefinition = new User(); + userDefinition.setId(GatewayTests.getUID()); + + // Upsert should create the user + User createdUser = client.upsertUser( + this.getDatabaseLink(this.databaseForTest, isNameBased), + userDefinition, null).getResource(); + + // Verify Id property + Assert.assertEquals(userDefinition.getId(), createdUser.getId()); + + // Read users to check the count and it should increase + users = client.readUsers(this.getDatabaseLink(this.databaseForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialUserCount + 1, users.size()); + + // Update user id + userDefinition.setId(GatewayTests.getUID()); + + // Upsert should create new user since id is changed + User newUser = client.upsertUser( + this.getDatabaseLink(this.databaseForTest, isNameBased), + userDefinition, null).getResource(); + + // Verify Id property + Assert.assertEquals(userDefinition.getId(), newUser.getId()); + + // Read users to check the count and it should increase + users = client.readUsers(this.getDatabaseLink(this.databaseForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialUserCount + 2, users.size()); + + // Delete users + client.deleteUser(this.getUserLink(this.databaseForTest, createdUser, isNameBased), null); + client.deleteUser(this.getUserLink(this.databaseForTest, newUser, isNameBased), null); + + // Read users to check the count and it should remain same + users = client.readUsers(this.getDatabaseLink(this.databaseForTest, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialUserCount, users.size()); + } + + @Test + public void testPermissionCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testPermissionCrud(isNameBased); + } + + @Test + public void testPermissionCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testPermissionCrud(isNameBased); + } + + private void testPermissionCrud(boolean isNameBased) throws DocumentClientException { + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Create user. - User user = client.createUser(databaseForTest.getSelfLink(), - new User("{ 'id': 'new user' }"), - null).getResource(); + User user = client.createUser(this.getDatabaseLink(this.databaseForTest, isNameBased), + new User("{ 'id': 'new user' }"), + null).getResource(); // List permissions. - List permissions = client.readPermissions(user.getSelfLink(), null).getQueryIterable().toList(); + List permissions = client.readPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), null).getQueryIterable().toList(); int beforeCreateCount = permissions.size(); Permission permissionDefinition = new Permission(); permissionDefinition.setId("new permission"); @@ -1344,51 +2235,159 @@ public void testPermissionCrud() throws DocumentClientException { permissionDefinition.setResourceLink("dbs/AQAAAA==/colls/AQAAAJ0fgTc="); // Create permission. - Permission permission = client.createPermission(user.getSelfLink(), permissionDefinition, null).getResource(); + Permission permission = client.createPermission(this.getUserLink(this.databaseForTest, user, isNameBased), permissionDefinition, null).getResource(); Assert.assertEquals("new permission", permission.getId()); // List permissions after creation. - permissions = client.readPermissions(user.getSelfLink(), null).getQueryIterable().toList(); + permissions = client.readPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), null).getQueryIterable().toList(); Assert.assertEquals(beforeCreateCount + 1, permissions.size()); // Query permissions. - permissions = client.queryPermissions(user.getSelfLink(), - new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter( - "@id", permission.getId()))), - null).getQueryIterable().toList(); + permissions = client.queryPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), + new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", + new SqlParameterCollection(new SqlParameter( + "@id", permission.getId()))), + null).getQueryIterable().toList(); Assert.assertEquals(1, permissions.size()); // Replace permission. permission.setId("replaced permission"); Permission replacedPermission = client.replacePermission( - permission, null).getResource(); + permission, null).getResource(); Assert.assertEquals("replaced permission", replacedPermission.getId()); Assert.assertEquals(permission.getId(), replacedPermission.getId()); // Read permission. - permission = client.readPermission(replacedPermission.getSelfLink(), null).getResource(); + permission = client.readPermission(this.getPermissionLink(this.databaseForTest, user, replacedPermission, isNameBased), null).getResource(); Assert.assertEquals(replacedPermission.getId(), permission.getId()); // Delete permission. - client.deletePermission(replacedPermission.getSelfLink(), null); + client.deletePermission(this.getPermissionLink(this.databaseForTest, user, replacedPermission, isNameBased), null); // Read permission after deletion. try { - client.readPermission(permission.getSelfLink(), null); + client.readPermission(this.getPermissionLink(this.databaseForTest, user, permission, isNameBased), null); Assert.fail("Exception didn't happen."); } catch (DocumentClientException e) { Assert.assertEquals(404, e.getStatusCode()); Assert.assertEquals("NotFound", e.getError().getCode()); } } + + // Upsert test for Permission resource - self link version + @Test + public void testPermissionUpsert_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testPermissionUpsert(isNameBased); + } + + // Upsert test for Permission resource - name based routing version + @Test + public void testPermissionUpsert_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testPermissionUpsert(isNameBased); + } - private void validateOfferResponseBody(Offer offer, String expectedCollLink, String expectedOfferType) { + private void testPermissionUpsert(boolean isNameBased) throws DocumentClientException { + // Create document client + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Create user definition + User userDefinition = new User(); + userDefinition.setId(GatewayTests.getUID()); + + // Create user. + User user = client.createUser( + this.getDatabaseLink(this.databaseForTest, isNameBased), + userDefinition, null).getResource(); + + // Read permissions and get initial count + List permissions = client + .readPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), null) + .getQueryIterable().toList(); + + int initialPermissionCount = permissions.size(); + + // Create permission definition + Permission permissionDefinition = new Permission(); + permissionDefinition.setId(GatewayTests.getUID()); + permissionDefinition.setPermissionMode(PermissionMode.Read); + permissionDefinition.setResourceLink("dbs/AQAAAA==/colls/AQAAAJ0fgTc="); + + // Upsert should create the permission + Permission createdPermission = client.upsertPermission( + this.getUserLink(this.databaseForTest, user, isNameBased), + permissionDefinition, null).getResource(); + + // Verify Id property + Assert.assertEquals(permissionDefinition.getId(), createdPermission.getId()); + + // Read permissions to check the count and it should increase + permissions = client.readPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialPermissionCount + 1, permissions.size()); + + // Update permission + createdPermission.setPermissionMode(PermissionMode.All); + + // Upsert should replace the permission since it already exists + Permission upsertedPermission = client.upsertPermission( + this.getUserLink(this.databaseForTest, user, isNameBased), + createdPermission, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdPermission.getId(), upsertedPermission.getId()); + + // Verify updated property + Assert.assertEquals(createdPermission.getPermissionMode(), upsertedPermission.getPermissionMode()); + + // Read permissions to check the count and it should remain the same + permissions = client.readPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialPermissionCount + 1, permissions.size()); + + // Update permission id + createdPermission.setId(GatewayTests.getUID()); + // ResourceLink needs to be changed along with the ID in order to create a new permission + createdPermission.setResourceLink("dbs/N9EdAA==/colls/N9EdAIugXgA="); + + // Upsert should create new permission since id is changed + Permission newPermission = client.upsertPermission( + this.getUserLink(this.databaseForTest, user, isNameBased), + createdPermission, null).getResource(); + + // Verify Id property + Assert.assertEquals(createdPermission.getId(), newPermission.getId()); + + // Verify ResourceLink property + Assert.assertEquals(createdPermission.getResourceLink(), newPermission.getResourceLink()); + + // Read permissions to check the count and it should increase + permissions = client.readPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialPermissionCount + 2, permissions.size()); + + // Delete permissions + client.deletePermission(this.getPermissionLink(this.databaseForTest, user, upsertedPermission, isNameBased), null); + client.deletePermission(this.getPermissionLink(this.databaseForTest, user, newPermission, isNameBased), null); + + // Read permissions to check the count and it should remain same + permissions = client.readPermissions(this.getUserLink(this.databaseForTest, user, isNameBased), null) + .getQueryIterable().toList(); + + Assert.assertEquals(initialPermissionCount, permissions.size()); + } + + private void validateOfferResponseBody(Offer offer, String expectedOfferType) { Assert.assertNotNull("Id cannot be null", offer.getId()); Assert.assertNotNull("Resource Id (Rid) cannot be null", offer.getResourceId()); Assert.assertNotNull("Self link cannot be null", offer.getSelfLink()); Assert.assertNotNull("Resource Link cannot be null", offer.getResourceLink()); Assert.assertTrue("Offer id not contained in offer self link", offer.getSelfLink().contains(offer.getId())); - Assert.assertEquals(StringUtils.removeEnd(StringUtils.removeStart(expectedCollLink, "/"), "/"), - StringUtils.removeEnd(StringUtils.removeStart(offer.getResourceLink(), "/"), "/")); if (expectedOfferType != null) { @@ -1399,18 +2398,31 @@ private void validateOfferResponseBody(Offer offer, String expectedCollLink, Str @Test public void testOfferReadAndQuery() throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); List offerList = client.readOffers(null).getQueryIterable().toList(); - Offer expectedOffer = offerList.get(0); + int originalOffersCount = offerList.size(); + Offer expectedOffer = null; + + String trimmedCollectionLink = StringUtils.removeEnd(StringUtils.removeStart(this.collectionForTest.getSelfLink(), "/"), "/"); + + for (Offer offer : offerList) { + String trimmedOfferResourceLink = StringUtils.removeEnd(StringUtils.removeStart(offer.getResourceLink(), "/"), "/"); + if (trimmedOfferResourceLink.equals(trimmedCollectionLink)) { + expectedOffer = offer; + break; + } + } + // There is an offer for the test collection we have created + Assert.assertNotNull(expectedOffer); - this.validateOfferResponseBody(expectedOffer, this.collectionForTest.getSelfLink(), null); + this.validateOfferResponseBody(expectedOffer, null); // Read the offer Offer readOffer = client.readOffer(expectedOffer.getSelfLink()).getResource(); - this.validateOfferResponseBody(readOffer, this.collectionForTest.getSelfLink(), expectedOffer.getOfferType()); + this.validateOfferResponseBody(readOffer, expectedOffer.getOfferType()); // Check if the read resource is what we expect Assert.assertEquals(expectedOffer.getId(), readOffer.getId()); Assert.assertEquals(expectedOffer.getResourceId(), readOffer.getResourceId()); @@ -1420,10 +2432,17 @@ public void testOfferReadAndQuery() throws DocumentClientException { // Query for the offer. List queryResultList = client.queryOffers( new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter("@id", expectedOffer.getId()))), + new SqlParameterCollection(new SqlParameter("@id", expectedOffer.getId()))), null).getQueryIterable().toList(); + + // We should find only one offer with the given id + Assert.assertEquals(queryResultList.size(), 1); Offer oneQueryResult = queryResultList.get(0); - this.validateOfferResponseBody(oneQueryResult, this.collectionForTest.getSelfLink(), expectedOffer.getOfferType()); + + String trimmedOfferResourceLink = StringUtils.removeEnd(StringUtils.removeStart(oneQueryResult.getResourceLink(), "/"), "/"); + Assert.assertTrue(trimmedCollectionLink.equals(trimmedOfferResourceLink)); + + this.validateOfferResponseBody(oneQueryResult, expectedOffer.getOfferType()); // Check if the query result is what we expect Assert.assertEquals(expectedOffer.getId(), oneQueryResult.getId()); Assert.assertEquals(expectedOffer.getResourceId(), oneQueryResult.getResourceId()); @@ -1452,9 +2471,9 @@ public void testOfferReadAndQuery() throws DocumentClientException { Assert.assertEquals(404, ex.getStatusCode()); } - // Make sure read feed returns 0 results. + // Make sure read offers returns one offer less that the original list of offers offerList = client.readOffers(null).getQueryIterable().toList(); - Assert.assertEquals(0, offerList.size()); + Assert.assertEquals(originalOffersCount-1, offerList.size()); } @Test @@ -1465,9 +2484,20 @@ public void testOfferReplace() throws DocumentClientException { ConsistencyLevel.Session); List offerList = client.readOffers(null).getQueryIterable().toList(); - Offer expectedOffer = offerList.get(0); + Offer expectedOffer = null; + + String trimmedCollectionLink = StringUtils.removeEnd(StringUtils.removeStart(this.collectionForTest.getSelfLink(), "/"), "/"); + + for (Offer offer : offerList) { + String trimmedOfferResourceLink = StringUtils.removeEnd(StringUtils.removeStart(offer.getResourceLink(), "/"), "/"); + if (trimmedOfferResourceLink.equals(trimmedCollectionLink)) { + expectedOffer = offer; + break; + } + } + Assert.assertNotNull(expectedOffer); - this.validateOfferResponseBody(expectedOffer, this.collectionForTest.getSelfLink(), null); + this.validateOfferResponseBody(expectedOffer, null); Offer offerToReplace = new Offer(expectedOffer.toString()); // Modify the offer @@ -1475,7 +2505,7 @@ public void testOfferReplace() throws DocumentClientException { // Replace the offer Offer replacedOffer = client.replaceOffer(offerToReplace).getResource(); - this.validateOfferResponseBody(replacedOffer, this.collectionForTest.getSelfLink(), "S2"); + this.validateOfferResponseBody(replacedOffer, "S2"); // Check if the replaced offer is what we expect Assert.assertEquals(offerToReplace.getId(), replacedOffer.getId()); @@ -1538,18 +2568,18 @@ public void testCreateCollectionWithOfferType() throws DocumentClientException { @Test public void testDatabaseAccount() throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); DatabaseAccount dba = client.getDatabaseAccount(); Assert.assertNotNull("dba Address link works", dba.getAddressesLink()); if (dba.getConsistencyPolicy().getDefaultConsistencyLevel() == ConsistencyLevel.BoundedStaleness) { Assert.assertTrue("StaleInternal should be larger than 5 seconds", - dba.getConsistencyPolicy().getMaxStalenessIntervalInSeconds() >= 5); + dba.getConsistencyPolicy().getMaxStalenessIntervalInSeconds() >= 5); Assert.assertTrue("StaleInternal boundness should be larger than 10", - dba.getConsistencyPolicy().getMaxStalenessPrefix() >= 10); + dba.getConsistencyPolicy().getMaxStalenessPrefix() >= 10); } } @@ -1558,9 +2588,9 @@ public void testDatabaseAccount() throws DocumentClientException { public void testAuthorization() throws DocumentClientException { // Sets up entities for this test. DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Create document1 Document document1 = client.createDocument( collectionForTest.getSelfLink(), @@ -1577,44 +2607,44 @@ public void testAuthorization() throws DocumentClientException { null).getResource(); // Create user1. User user1 = client.createUser(databaseForTest.getSelfLink(), - new User("{ 'id': 'user1' }"), - null).getResource(); + new User("{ 'id': 'user1' }"), + null).getResource(); Permission permission1Definition = new Permission(String.format( "{" + - " 'id': 'permission On Coll1'," + - " 'permissionMode': 'Read'," + - " 'resource': '%s'" + - "}", collectionForTest.getSelfLink())); + " 'id': 'permission On Coll1'," + + " 'permissionMode': 'Read'," + + " 'resource': '%s'" + + "}", collectionForTest.getSelfLink())); // Create permission for collectionForTest Permission permission1 = client.createPermission(user1.getSelfLink(), - permission1Definition, - null).getResource(); + permission1Definition, + null).getResource(); // Create user 2. User user2 = client.createUser(databaseForTest.getSelfLink(), - new User("{ 'id': 'user2' }"), - null).getResource(); + new User("{ 'id': 'user2' }"), + null).getResource(); Permission permission2Definition = new Permission(String.format( "{" + - " 'id': 'permission On coll2'," + - " 'permissionMode': 'All'," + - " 'resource': '%s'" + - "}", anotherCollectionForTest.getSelfLink())); + " 'id': 'permission On coll2'," + + " 'permissionMode': 'All'," + + " 'resource': '%s'" + + "}", anotherCollectionForTest.getSelfLink())); // Create permission on anotherCollectionForTest. Permission permission2 = client.createPermission(user2.getSelfLink(), - permission2Definition, - null).getResource(); + permission2Definition, + null).getResource(); // Now the resources for this test is fully prepared. // Client without any authorization will fail. DocumentClient badClient = new DocumentClient(HOST, - "", - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + "", + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); try { badClient.readDatabases(null).getQueryIterable().toList(); @@ -1627,9 +2657,9 @@ public void testAuthorization() throws DocumentClientException { List resourceTokens1 = new ArrayList(); resourceTokens1.add(permission1); DocumentClient clientForCollection1 = new DocumentClient(HOST, - resourceTokens1, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + resourceTokens1, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // 1. Success-- Use Col1 Permission to Read DocumentCollection obtainedCollection1 = @@ -1645,53 +2675,64 @@ public void testAuthorization() throws DocumentClientException { // 3. Success-- Use Col1 Permission to Read All Docs Assert.assertEquals("Expected 2 Documents to be succesfully read", - 2, - clientForCollection1.readDocuments(obtainedCollection1.getSelfLink(), - null).getQueryIterable().toList().size()); + 2, + clientForCollection1.readDocuments(obtainedCollection1.getSelfLink(), + null).getQueryIterable().toList().size()); // 4. Success-- Use Col1 Permission to Read Col1Doc1 Document successDoc = clientForCollection1.readDocument(document1.getSelfLink(), null).getResource(); Assert.assertEquals("Expected to read children using parent permissions", - document1.getId(), - successDoc.getId()); + document1.getId(), + successDoc.getId()); // Client with full access on the anotherDocumentForTest. List resourceTokens2 = new ArrayList(); resourceTokens2.add(permission2); DocumentClient clientForCollection2 = new DocumentClient(HOST, - resourceTokens2, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + resourceTokens2, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); Document newDocument2 = new Document(String.format( "{" + - " 'CustomProperty1': 'BBBBBB'," + - " 'customProperty2': 1000," + - " 'id': '%s'" + - "}", document2.getId())); // Same id. + " 'CustomProperty1': 'BBBBBB'," + + " 'customProperty2': 1000," + + " 'id': '%s'" + + "}", document2.getId())); // Same id. successDoc = clientForCollection2.createDocument(anotherCollectionForTest.getSelfLink(), - newDocument2, - null, - true).getResource(); + newDocument2, + null, + true).getResource(); Assert.assertEquals("document should have been created successfully", - "BBBBBB", - successDoc.getString("CustomProperty1")); + "BBBBBB", + successDoc.getString("CustomProperty1")); } @Test - public void testConflictCrud() throws DocumentClientException { + public void testConflictCrud_SelfLink() throws DocumentClientException { + boolean isNameBased = false; + testConflictCrud(isNameBased); + } + + @Test + public void testConflictCrud_NameBased() throws DocumentClientException { + boolean isNameBased = true; + testConflictCrud(isNameBased); + } + + private void testConflictCrud(boolean isNameBased) throws DocumentClientException { DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Read conflicts. - client.readConflicts(this.collectionForTest.getSelfLink(), null).getQueryIterable().toList(); + client.readConflicts(this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), null).getQueryIterable().toList(); // Query for conflicts. client.queryConflicts( - this.collectionForTest.getSelfLink(), + this.getDocumentCollectionLink(this.databaseForTest, this.collectionForTest, isNameBased), new SqlQuerySpec("SELECT * FROM root r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter("@id", "FakeId"))), + new SqlParameterCollection(new SqlParameter("@id", "FakeId"))), null).getQueryIterable().toList(); } @@ -1763,8 +2804,8 @@ public void testIndexProgressHeaders() throws DocumentClientException { doc.set("name", "name01"); SqlQuerySpec query = new SqlQuerySpec( - "SELECT * FROM root r WHERE r.name=@name", - new SqlParameterCollection(new SqlParameter("@name", "name01"))); + "SELECT * FROM root r WHERE r.name=@name", + new SqlParameterCollection(new SqlParameter("@name", "name01"))); // Consistent-indexing collection { @@ -1773,6 +2814,18 @@ public void testIndexProgressHeaders() throws DocumentClientException { consistentCollection = client.createCollection(this.databaseForTest.getSelfLink(), consistentCollection, null).getResource(); ResourceResponse collectionResponse = client.readCollection(consistentCollection.getSelfLink(), null); Assert.assertEquals(100, collectionResponse.getIndexTransformationProgress()); + Assert.assertEquals(-1, collectionResponse.getLazyIndexingProgress()); + } + + // Lazy-indexing collection + { + DocumentCollection lazyCollection = new DocumentCollection(); + lazyCollection.setId("Lazy Collection"); + lazyCollection.getIndexingPolicy().setIndexingMode(IndexingMode.Lazy); + lazyCollection = client.createCollection(this.databaseForTest.getSelfLink(), lazyCollection, null).getResource(); + ResourceResponse collectionResponse = client.readCollection(lazyCollection.getSelfLink(), null); + Assert.assertEquals(100, collectionResponse.getIndexTransformationProgress()); + Assert.assertTrue(collectionResponse.getLazyIndexingProgress() >= 0 && collectionResponse.getLazyIndexingProgress() <= 100); } // None-indexing collection @@ -1784,16 +2837,17 @@ public void testIndexProgressHeaders() throws DocumentClientException { noneCollection = client.createCollection(this.databaseForTest.getSelfLink(), noneCollection, null).getResource(); ResourceResponse collectionResponse = client.readCollection(noneCollection.getSelfLink(), null); Assert.assertEquals(100, collectionResponse.getIndexTransformationProgress()); + Assert.assertEquals(-1, collectionResponse.getLazyIndexingProgress()); } } - + @Test public void testIdValidation() throws DocumentClientException { // Sets up entities for this test. DocumentClient client = new DocumentClient(HOST, - MASTER_KEY, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); // Id shouldn't end with a space. try { client.createDocument( @@ -1839,5 +2893,224 @@ public void testIdValidation() throws DocumentClientException { } catch (IllegalArgumentException e) { Assert.assertEquals("Id contains illagel chars.", e.getMessage()); } + // Id can begin with a space. + client.createDocument( + collectionForTest.getSelfLink(), + new Document("{ 'id': ' id_begin_space', 'key': 'value' }"), null, false).getResource(); + Assert.assertTrue(true); + + // Id can contain space within. + client.createDocument( + collectionForTest.getSelfLink(), + new Document("{ 'id': 'id containing space', 'key': 'value' }"), null, false).getResource(); + Assert.assertTrue(true); + } + + @Test + public void testIdCaseValidation() throws DocumentClientException { + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + List collections = client.readCollections(this.getDatabaseLink(this.databaseForTest, true), + null).getQueryIterable().toList(); + + int beforeCreateCollectionsCount = collections.size(); + + // pascalCase + DocumentCollection collectionDefinition1 = new DocumentCollection(); + collectionDefinition1.setId("sampleCollection"); + + // CamelCase + DocumentCollection collectionDefinition2 = new DocumentCollection(); + collectionDefinition2.setId("SampleCollection"); + + // Create 2 collections with different casing of IDs + DocumentCollection createdCollection1 = client.createCollection(getDatabaseLink(this.databaseForTest, true), + collectionDefinition1, + null).getResource(); + + DocumentCollection createdCollection2 = client.createCollection(getDatabaseLink(this.databaseForTest, true), + collectionDefinition2, + null).getResource(); + + // Verify if additional 2 collections got created + collections = client.readCollections(this.getDatabaseLink(this.databaseForTest, true), null).getQueryIterable().toList(); + Assert.assertEquals(collections.size(), beforeCreateCollectionsCount+2); + + // Verify that collections are created with specified IDs + Assert.assertEquals(collectionDefinition1.getId(), createdCollection1.getId()); + Assert.assertEquals(collectionDefinition2.getId(), createdCollection2.getId()); + } + + @Test + public void testIdUnicodeValidation() throws DocumentClientException { + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + // Unicode chars in Hindi for Id which translates to: "Hindi is one of the main languages of India" + DocumentCollection collectionDefinition1 = new DocumentCollection(); + collectionDefinition1.setId("हिन्दी भारत के मुख्य भाषाओं में से एक है"); + + // Special chars for Id + DocumentCollection collectionDefinition2 = new DocumentCollection(); + collectionDefinition2.setId("!@$%^&*()-~`'_[]{}|;:,.<>"); + + // verify that collections are created with specified IDs + DocumentCollection createdCollection1 = client.createCollection(getDatabaseLink(this.databaseForTest, true), + collectionDefinition1, + null).getResource(); + + DocumentCollection createdCollection2 = client.createCollection(getDatabaseLink(this.databaseForTest, true), + collectionDefinition2, + null).getResource(); + + // Verify that collections are created with specified IDs + Assert.assertEquals(collectionDefinition1.getId(), createdCollection1.getId()); + Assert.assertEquals(collectionDefinition2.getId(), createdCollection2.getId()); + } + + @Test + public void testSessionContainer() throws DocumentClientException { + DocumentClient client = new DocumentClient(HOST, + MASTER_KEY, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + DocumentCollection collectionDefinition = new DocumentCollection(); + collectionDefinition.setId(GatewayTests.getUID()); + DocumentCollection collection = client.createCollection(this.getDatabaseLink(this.databaseForTest, true), + collectionDefinition, + null).getResource(); + // create a document + Document documentDefinition = new Document( + "{" + + " 'name': 'sample document'," + + " 'key': '0'" + + "}"); + + Document document = client.createDocument(this.getDocumentCollectionLink(this.databaseForTest, collection, false), + documentDefinition, + null, + false).getResource(); + + + // Replace document. + for(int i=0; i<100; i++) { + + // Update the "key" property in a tight loop + document.set("key", Integer.toString(i)); + // Replace the document + Document replacedDocument = client.replaceDocument(document, null).getResource(); + + // Read the document. + Document documentFromRead = client.readDocument(this.getDocumentLink(this.databaseForTest, collection, replacedDocument, true), null).getResource(); + // Verify that we read our own write(key property) + Assert.assertEquals(replacedDocument.getString("key"), documentFromRead.getString("key")); + } + } + + private String getDatabaseLink(Database database, boolean isNameBased) + { + if(isNameBased) { + return DATABASES_PATH_SEGMENT + "/" + database.getId(); + } + else { + return database.getSelfLink(); + } + } + + private String getUserLink(Database database, User user, boolean isNameBased) + { + if(isNameBased) { + return this.getDatabaseLink(database, true) + "/" + USERS_PATH_SEGMENT + "/" + user.getId(); + } + else { + return user.getSelfLink(); + } + } + + private String getPermissionLink(Database database, User user, Permission permission, boolean isNameBased) + { + if(isNameBased) { + return this.getUserLink(database, user, true) + "/" + PERMISSIONS_PATH_SEGMENT + "/" + permission.getId(); + } + else { + return permission.getSelfLink(); + } + } + + private String getDocumentCollectionLink(Database database, DocumentCollection coll, boolean isNameBased) + { + if(isNameBased) { + return this.getDatabaseLink(database, true) + "/" + COLLECTIONS_PATH_SEGMENT + "/" + coll.getId(); + } + else { + return coll.getSelfLink(); + } + } + + private String getDocumentLink(Database database, DocumentCollection coll, Document doc, boolean isNameBased) + { + if(isNameBased) { + return this.getDocumentCollectionLink(database, coll, true) + "/" + DOCUMENTS_PATH_SEGMENT + "/" + doc.getId(); + } + else { + return doc.getSelfLink(); + } + } + + private String getAttachmentLink(Database database, DocumentCollection coll, Document doc, Attachment attachment, boolean isNameBased) + { + if(isNameBased) { + return this.getDocumentLink(database, coll, doc, true) + "/" + ATTACHMENTS_PATH_SEGMENT + "/" + attachment.getId(); + } + else { + return attachment.getSelfLink(); + } + } + + private String getTriggerLink(Database database, DocumentCollection coll, Trigger trigger, boolean isNameBased) + { + if(isNameBased) { + return this.getDocumentCollectionLink(database, coll, true) + "/" + TRIGGERS_PATH_SEGMENT + "/" + trigger.getId(); + } + else { + return trigger.getSelfLink(); + } + } + + private String getStoredProcedureLink(Database database, DocumentCollection coll, StoredProcedure storedProcedure, boolean isNameBased) + { + if(isNameBased) { + return this.getDocumentCollectionLink(database, coll, true) + "/" + STORED_PROCEDURES_PATH_SEGMENT + "/" + storedProcedure.getId(); + } + else { + return storedProcedure.getSelfLink(); + } + } + + private String getUserDefinedFunctionLink(Database database, DocumentCollection coll, UserDefinedFunction userDefinedFunction, boolean isNameBased) + { + if(isNameBased) { + return this.getDocumentCollectionLink(database, coll, true) + "/" + USER_DEFINED_FUNCTIONS_PATH_SEGMENT + "/" + userDefinedFunction.getId(); + } + else { + return userDefinedFunction.getSelfLink(); + } + } + + @SuppressWarnings("unused") + private String getConflictLink(Database database, DocumentCollection coll, Conflict conflict, boolean isNameBased) + { + if(isNameBased) { + return this.getDocumentCollectionLink(database, coll, true) + "/" + CONFLICTS_PATH_SEGMENT + "/" + conflict.getId(); + } + else { + return conflict.getSelfLink(); + } } }