All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.documentdb.DocumentClient Maven / Gradle / Ivy

/*
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 */

package com.microsoft.azure.documentdb;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.microsoft.azure.documentdb.internal.BridgeInternal;
import com.microsoft.azure.documentdb.internal.RefreshAuthTokenDelegate;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.documentdb.internal.AbstractDocumentServiceRequest;
import com.microsoft.azure.documentdb.internal.AuthorizationTokenProvider;
import com.microsoft.azure.documentdb.internal.BaseAuthorizationTokenProvider;
import com.microsoft.azure.documentdb.internal.BaseDatabaseAccountConfigurationProvider;
import com.microsoft.azure.documentdb.internal.CollectionCacheInternal;
import com.microsoft.azure.documentdb.internal.DatabaseAccountConfigurationProvider;
import com.microsoft.azure.documentdb.internal.DocumentServiceRequest;
import com.microsoft.azure.documentdb.internal.DocumentServiceResponse;
import com.microsoft.azure.documentdb.internal.EndpointManager;
import com.microsoft.azure.documentdb.internal.GatewayProxy;
import com.microsoft.azure.documentdb.internal.HttpConstants;
import com.microsoft.azure.documentdb.internal.OperationType;
import com.microsoft.azure.documentdb.internal.PathParser;
import com.microsoft.azure.documentdb.internal.Paths;
import com.microsoft.azure.documentdb.internal.QueryCompatibilityMode;
import com.microsoft.azure.documentdb.internal.ResourceType;
import com.microsoft.azure.documentdb.internal.RetryCreateDocumentDelegate;
import com.microsoft.azure.documentdb.internal.RetryRequestDelegate;
import com.microsoft.azure.documentdb.internal.RetryUtility;
import com.microsoft.azure.documentdb.internal.RuntimeConstants;
import com.microsoft.azure.documentdb.internal.ServiceJNIWrapper;
import com.microsoft.azure.documentdb.internal.SessionContainer;
import com.microsoft.azure.documentdb.internal.SessionTokenHelper;
import com.microsoft.azure.documentdb.internal.StoreModel;
import com.microsoft.azure.documentdb.internal.UserAgentContainer;
import com.microsoft.azure.documentdb.internal.Utils;
import com.microsoft.azure.documentdb.internal.directconnectivity.AddressCache;
import com.microsoft.azure.documentdb.internal.directconnectivity.GlobalAddressResolver;
import com.microsoft.azure.documentdb.internal.directconnectivity.HttpClientFactory;
import com.microsoft.azure.documentdb.internal.directconnectivity.HttpTransportClient;
import com.microsoft.azure.documentdb.internal.directconnectivity.ServerStoreModel;
import com.microsoft.azure.documentdb.internal.directconnectivity.TransportClient;
import com.microsoft.azure.documentdb.internal.query.PartitionedQueryExecutionInfo;
import com.microsoft.azure.documentdb.internal.query.QueryPartitionProvider;
import com.microsoft.azure.documentdb.internal.routing.ClientCollectionCache;
import com.microsoft.azure.documentdb.internal.routing.CollectionCache;
import com.microsoft.azure.documentdb.internal.routing.PartitionKeyInternal;
import com.microsoft.azure.documentdb.internal.routing.PartitionKeyRangeCache;
import com.microsoft.azure.documentdb.internal.routing.PartitionKeyRangeIdentity;
import com.microsoft.azure.documentdb.internal.routing.RoutingMapProvider;
import com.microsoft.azure.documentdb.internal.routing.RoutingMapProviderHelper;

/**
 * Provides a client-side logical representation of the Azure Cosmos DB service. This client is used to configure and
 * execute requests against the service.
 * 

* The service client encapsulates the endpoint and credentials used to access the Azure Cosmos DB service. */ public class DocumentClient implements AutoCloseable, CollectionCacheInternal { @Deprecated protected static final String PartitionResolverErrorMessage = "Couldn't find any partition resolvers for the database link provided. Ensure that the link you used when registering the partition resolvers matches the link provided or you need to register both types of database link(self link as well as ID based link)."; private static final Logger logger = LoggerFactory.getLogger(DocumentClient.class); private URI serviceEndpoint; private String masterKey; private Map resourceTokens; private ConnectionPolicy connectionPolicy; private GatewayProxy gatewayProxy; private SessionContainer sessionContainer; private ConsistencyLevel desiredConsistencyLevel; private EndpointManager globalEndpointManager; @SuppressWarnings("deprecation") private ConcurrentHashMap partitionResolvers; private StoreModel storeModel; private GlobalAddressResolver globalAddressResolver; private TransportClient transportClient; private AuthorizationTokenProvider authorizationTokenProvider; private DatabaseAccountConfigurationProvider databaseAccountConfigurationProvider; private ClientCollectionCache collectionCache; private PartitionKeyRangeCache partitionKeyRangeCache; private PoolingHttpClientConnectionManager poolingHttpClientConnectionManager; private ExecutorService executorService; private ObjectMapper objectMapper; private boolean useMultipleWriteLocations; /** * Compatibility mode: * Allows to specify compatibility mode used by client when making query requests. Should be removed when * application/sql is no longer supported. */ private QueryCompatibilityMode queryCompatibilityMode = QueryCompatibilityMode.Default; /** * Initializes a new instance of the DocumentClient class using the specified Azure Cosmos DB service endpoint and keys. * * @param serviceEndpoint the URI of the service end point. * @param masterKey the master key. * @param connectionPolicy the connection policy. * @param desiredConsistencyLevel the desired consistency level. */ public DocumentClient(String serviceEndpoint, String masterKey, ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel) { this(serviceEndpoint, masterKey, null, connectionPolicy, desiredConsistencyLevel, null, null, new UserAgentContainer()); } /** * Initializes a new instance of the DocumentClient class using the specified Azure Cosmos DB service endpoint and keys. * * @param serviceEndpoint the URI of the service end point. * @param masterKey the master key. * @param objectMapper the custom objectMapper * @param connectionPolicy the connection policy. * @param desiredConsistencyLevel the desired consistency level. */ public DocumentClient(String serviceEndpoint, String masterKey, ObjectMapper objectMapper, ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel) { this(serviceEndpoint, masterKey, objectMapper, connectionPolicy, desiredConsistencyLevel, null, null, new UserAgentContainer()); } /** * Initializes a new instance of the Microsoft.Azure.Documents.Client.DocumentClient class using the specified * Azure Cosmos DB service endpoint and permissions. * * @param serviceEndpoint the URI of the service end point. * @param permissionFeed the permission feed. * @param connectionPolicy the connection policy. * @param desiredConsistencyLevel the desired consistency level. */ public DocumentClient(String serviceEndpoint, List permissionFeed, ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel) { this(serviceEndpoint, permissionFeed, null, connectionPolicy, desiredConsistencyLevel, new UserAgentContainer()); } /** * Initializes a new instance of the Microsoft.Azure.Documents.Client.DocumentClient class using the specified * Azure Cosmos DB service endpoint and permissions. * * @param serviceEndpoint the URI of the service end point. * @param permissionFeed the permission feed. * @param objectMapper the custom objectMapper * @param connectionPolicy the connection policy. * @param desiredConsistencyLevel the desired consistency level. */ public DocumentClient(String serviceEndpoint, List permissionFeed, ObjectMapper objectMapper, ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel) { this(serviceEndpoint, permissionFeed, objectMapper, connectionPolicy, desiredConsistencyLevel, new UserAgentContainer()); } DocumentClient(String serviceEndpoint, List permissionFeed, ObjectMapper objectMapper, ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel, UserAgentContainer userAgentContainer) { URI uri = null; try { uri = new URI(serviceEndpoint); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid serviceEndPoint.", e); } this.resourceTokens = new HashMap(); this.objectMapper = objectMapper != null ? objectMapper: new ObjectMapper(); for (Permission permission : permissionFeed) { String[] segments = StringUtils.split(permission.getResourceLink(), '/'); if (segments.length <= 0) { throw new IllegalArgumentException("link"); } String resourceId = segments[segments.length - 1]; this.resourceTokens.put(resourceId, permission.getToken()); } this.initialize(uri, connectionPolicy, desiredConsistencyLevel, userAgentContainer, null); } DocumentClient(String serviceEndpoint, String masterKey, ObjectMapper objectMapper, ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel, ConcurrentHashMap addressCaches, TransportClient transportClient, UserAgentContainer userAgentContainer) { URI uri = null; try { uri = new URI(serviceEndpoint); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid serviceEndPoint.", e); } if (StringUtils.isEmpty(masterKey)) { throw new IllegalArgumentException("Invalid authentication key."); } this.masterKey = masterKey; this.objectMapper = objectMapper != null ? objectMapper: new ObjectMapper(); this.transportClient = transportClient; this.initialize(uri, connectionPolicy, desiredConsistencyLevel, userAgentContainer, addressCaches); } private static String serializeProcedureParams(Object[] objectArray, ObjectMapper objectMapper) { String[] stringArray = new String[objectArray.length]; for (int i = 0; i < objectArray.length; ++i) { Object object = objectArray[i]; if (object instanceof JsonSerializable) { stringArray[i] = ((JsonSerializable) object).toJson(); } else if (object instanceof JSONObject){ stringArray[i] = object.toString(); } else { // POJO, number, String or Boolean try { stringArray[i] = objectMapper.writeValueAsString(object); } catch (IOException e) { throw new IllegalArgumentException("Can't serialize the object into the json string", e); } } } return String.format("[%s]", StringUtils.join(stringArray, ",")); } static void validateResource(Resource resource) { if (!StringUtils.isEmpty(resource.getId())) { if (resource.getId().indexOf('/') != -1 || resource.getId().indexOf('\\') != -1 || resource.getId().indexOf('?') != -1 || resource.getId().indexOf('#') != -1) { throw new IllegalArgumentException("Id contains illegal chars."); } if (resource.getId().endsWith(" ")) { throw new IllegalArgumentException("Id ends with a space."); } } } @SuppressWarnings("deprecation") // use of PartitionResolver is deprecated. private void initialize(URI serviceEndpoint, ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel, UserAgentContainer userAgentContainer, ConcurrentHashMap addressCaches) { logger.info("Initializing DocumentClient with" + " serviceEndpoint [{}], ConnectionPolicy [{}], ConsistencyLevel [{}]", serviceEndpoint, connectionPolicy, desiredConsistencyLevel); this.serviceEndpoint = serviceEndpoint; if (connectionPolicy != null) { this.connectionPolicy = connectionPolicy; } else { this.connectionPolicy = new ConnectionPolicy(); } this.desiredConsistencyLevel = desiredConsistencyLevel; String userAgentSuffix = this.connectionPolicy.getUserAgentSuffix(); if (userAgentSuffix != null && userAgentSuffix.length() > 0) { userAgentContainer.setSuffix(userAgentSuffix); } this.executorService = new DocumentDBThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); this.poolingHttpClientConnectionManager = HttpClientFactory.createConnectionManager( this.connectionPolicy.getMaxPoolSize(), this.connectionPolicy.getIdleConnectionTimeout(), this.connectionPolicy.getRequestTimeout()); HttpClient httpClient = createHttpClient(this.poolingHttpClientConnectionManager, this.connectionPolicy.getRequestTimeout()); HttpClient mediaHttpClient = HttpClientFactory.createHttpClient(this.poolingHttpClientConnectionManager, this.connectionPolicy.getMediaRequestTimeout()); this.globalEndpointManager = new GlobalEndpointManager(this); // use of PartitionResolver is deprecated. this.partitionResolvers = new ConcurrentHashMap(); this.authorizationTokenProvider = new BaseAuthorizationTokenProvider(this.masterKey, this.resourceTokens); this.collectionCache = createClientCollectionCache(this, this.executorService); this.partitionKeyRangeCache = createPartitionKeyRangeCache(); this.sessionContainer = new SessionContainer( this.serviceEndpoint.getHost(), this.collectionCache, this.partitionKeyRangeCache); this.gatewayProxy = createGatewayProxy( this.connectionPolicy, desiredConsistencyLevel, this.queryCompatibilityMode, this.masterKey, this.resourceTokens, userAgentContainer, this.globalEndpointManager, httpClient, mediaHttpClient, this.sessionContainer); this.databaseAccountConfigurationProvider = createBaseDatabaseAccountConfigurationProvider( this.desiredConsistencyLevel, this.globalEndpointManager); if (this.connectionPolicy.getConnectionMode() == ConnectionMode.DirectHttps) { if (this.globalAddressResolver == null) { this.globalAddressResolver = new GlobalAddressResolver(this.serviceEndpoint.toString(), addressCaches, this.connectionPolicy, this.collectionCache, this.partitionKeyRangeCache, userAgentContainer, this.authorizationTokenProvider, httpClient, this.globalEndpointManager, this, this.executorService); } if (this.transportClient == null) { this.transportClient = createHttpTransportClient(this.connectionPolicy, userAgentContainer); } this.storeModel = createServerStoreModel( this.transportClient, this.globalAddressResolver, this.sessionContainer, this.databaseAccountConfigurationProvider, this.authorizationTokenProvider, this.executorService, this.connectionPolicy.getRetryOptions(), this.useMultipleWriteLocations && this.desiredConsistencyLevel != ConsistencyLevel.Strong); } else { this.storeModel = this.gatewayProxy; } } HttpClient createHttpClient(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager, int requestTimeout) { return HttpClientFactory.createHttpClient(poolingHttpClientConnectionManager, requestTimeout); } DatabaseAccountConfigurationProvider createBaseDatabaseAccountConfigurationProvider( ConsistencyLevel desiredConsistencyLevel, EndpointManager globalEndpointManager) { return new BaseDatabaseAccountConfigurationProvider(desiredConsistencyLevel, globalEndpointManager); } ClientCollectionCache createClientCollectionCache(DocumentClient documentClient, ExecutorService executorService) { return new ClientCollectionCache(documentClient, executorService); } GatewayProxy createGatewayProxy(ConnectionPolicy connectionPolicy, ConsistencyLevel desiredConsistencyLevel, QueryCompatibilityMode queryCompatibilityMode, String masterKey, Map resourceTokens, UserAgentContainer userAgentContainer, EndpointManager globalEndpointManager, HttpClient httpClient, HttpClient mediaHttpClient, SessionContainer sessionContainer) { return new GatewayProxy(connectionPolicy, desiredConsistencyLevel, queryCompatibilityMode, masterKey, resourceTokens, userAgentContainer, globalEndpointManager, httpClient, mediaHttpClient, sessionContainer); } StoreModel createServerStoreModel(TransportClient transportClient, GlobalAddressResolver globalAddressResolver, SessionContainer sessionContainer, DatabaseAccountConfigurationProvider databaseAccountConfigurationProvider, AuthorizationTokenProvider authorizationTokenProvider, ExecutorService executorService, RetryOptions retryOptions, boolean useMultipleWriteLocations) { RefreshAuthTokenDelegate refreshAuthTokenDelegate = new RefreshAuthTokenDelegate() { @Override public void apply(DocumentServiceRequest request){ putMoreContentIntoDocumentServiceRequest(request, null); } }; return new ServerStoreModel(transportClient, globalAddressResolver, sessionContainer, databaseAccountConfigurationProvider, authorizationTokenProvider, executorService, refreshAuthTokenDelegate, retryOptions, useMultipleWriteLocations); } HttpTransportClient createHttpTransportClient(ConnectionPolicy policy, UserAgentContainer userAgentContainer) { return new HttpTransportClient(this.connectionPolicy, userAgentContainer); } @Deprecated public void registerPartitionResolver(String databaseLink, PartitionResolver partitionResolver) throws DocumentClientException { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } if (partitionResolver == null) { throw new IllegalArgumentException("partitionResolver"); } this.partitionResolvers.put(Utils.trimBeginingAndEndingSlashes(databaseLink), partitionResolver); } @Deprecated protected PartitionResolver getPartitionResolver(String databaseLink) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } return this.partitionResolvers.get(Utils.trimBeginingAndEndingSlashes(databaseLink)); } QueryCompatibilityMode getQueryCompatiblityMode() { return this.queryCompatibilityMode; } /** * Gets the default service endpoint as passed in by the user during construction. * * @return the service endpoint URI */ public URI getServiceEndpoint() { return this.serviceEndpoint; } /** * Gets the current write endpoint chosen based on availability and preference. * * @return the write endpoint URI */ public URI getWriteEndpoint() { return this.globalEndpointManager.getWriteEndpoint(); } /** * Gets the current read endpoint chosen based on availability and preference. * * @return the read endpoint URI */ public URI getReadEndpoint() { return this.globalEndpointManager.getReadEndpoint(); } public ConnectionPolicy getConnectionPolicy() { return this.connectionPolicy; } EndpointManager getEndpointManager() { return this.globalEndpointManager; } void setEndpointManager(EndpointManager endpointManager) { this.globalEndpointManager = endpointManager; } RoutingMapProvider getPartitionKeyRangeCache() { return this.partitionKeyRangeCache; } CollectionCache getCollectionCache() { return this.collectionCache; } ExecutorService getExecutorService() { return this.executorService; } String getSessionToken(String collectionLink) { return this.sessionContainer.resolveGlobalSessionToken(collectionLink); } PoolingHttpClientConnectionManager getPoolHttpClientConnectionManager() { return this.poolingHttpClientConnectionManager; } // used by unit test to mock gateway proxy. void setGatewayProxyOverride(GatewayProxy proxyOverride) { this.gatewayProxy = proxyOverride; if (this.connectionPolicy.getConnectionMode() != ConnectionMode.DirectHttps) { this.storeModel = proxyOverride; } } // used by unit test to mock server store model. void setStoreModel(StoreModel storeModel) { if (this.connectionPolicy.getConnectionMode() == ConnectionMode.DirectHttps) { this.storeModel = storeModel; } } /** * Creates a database. * * @param database the database. * @param options the request options. * @return the resource response with the created database. * @throws DocumentClientException the document client exception. */ public ResourceResponse createDatabase(Database database, RequestOptions options) throws DocumentClientException { if (database == null) { throw new IllegalArgumentException("Database"); } logger.debug("Creating a Database. id: [{}]", database.getId()); DocumentClient.validateResource(database); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Create, ResourceType.Database, Paths.DATABASES_ROOT, database, requestHeaders); return new ResourceResponse(this.doCreate(request), Database.class); } /** * Deletes a database. * * @param databaseLink the database link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteDatabase(String databaseLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } logger.debug("Deleting a Database. databaseLink: [{}]", databaseLink); String path = Utils.joinPath(databaseLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.Database, path, requestHeaders); return new ResourceResponse(this.doDelete(request), Database.class); } /** * Reads a database. * * @param databaseLink the database link. * @param options the request options. * @return the resource response with the read database. * @throws DocumentClientException the document client exception. */ public ResourceResponse readDatabase(String databaseLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } logger.debug("Reading a Database. databaseLink: [{}]", databaseLink); String path = Utils.joinPath(databaseLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Database, path, requestHeaders); return new ResourceResponse(this.doRead(request), Database.class); } /** * Reads all databases. * * @param options the feed options. * @return the feed response with the read databases. */ public FeedResponse readDatabases(FeedOptions options) { logger.debug("Reading Databases."); return new FeedResponse(new QueryIterable(this, ResourceType.Database, Database.class, Paths.DATABASES_ROOT, options)); } /** * Query for databases. * * @param query the query. * @param options the feed options. * @return the feed response with the obtained databases. */ public FeedResponse queryDatabases(String query, FeedOptions options) { if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryDatabases(new SqlQuerySpec(query, null), options); } /** * Query for databases. * * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained databases. */ public FeedResponse queryDatabases(SqlQuerySpec querySpec, FeedOptions options) { if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Databases. querySpec: [{}]", querySpec); return new FeedResponse(new QueryIterable(this, ResourceType.Database, Database.class, Paths.DATABASES_ROOT, querySpec, options)); } /** * Creates a document collection. * * @param databaseLink the database link. * @param collection the collection. * @param options the request options. * @return the resource response with the created collection. * @throws DocumentClientException the document client exception. */ public ResourceResponse createCollection(String databaseLink, DocumentCollection collection, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } if (collection == null) { throw new IllegalArgumentException("collection"); } logger.debug("Creating a Collection. databaseLink: [{}], Collection id: [{}]", databaseLink, collection.getId()); DocumentClient.validateResource(collection); String path = Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Create, ResourceType.DocumentCollection, path, collection, requestHeaders); return new ResourceResponse(this.doCreate(request), DocumentCollection.class); } /** * Replaces a document collection. * * @param collection the document collection to use. * @param options the request options. * @return the resource response with the replaced document collection. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceCollection(DocumentCollection collection, RequestOptions options) throws DocumentClientException { if (collection == null) { throw new IllegalArgumentException("collection"); } logger.debug("Replacing a Collection. id: [{}]", collection.getId()); DocumentClient.validateResource(collection); String path = Utils.joinPath(collection.getSelfLink(), null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.DocumentCollection, path, collection, requestHeaders); return new ResourceResponse(this.doReplace(request), DocumentCollection.class); } /** * Deletes a document collection by the collection link. * * @param collectionLink the collection link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteCollection(String collectionLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.debug("Deleting a Collection. collectionLink: [{}]", collectionLink); String path = Utils.joinPath(collectionLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.DocumentCollection, path, requestHeaders); return new ResourceResponse(this.doDelete(request), DocumentCollection.class); } /** * Reads a document collection by the collection link. * * @param collectionLink the collection link. * @param options the request options. * @return the resource response with the read collection. * @throws DocumentClientException the document client exception. */ @Override public ResourceResponse readCollection(String collectionLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.debug("Reading a Collection. collectionLink: [{}]", collectionLink); String path = Utils.joinPath(collectionLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.DocumentCollection, path, requestHeaders); return new ResourceResponse(this.doRead(request), DocumentCollection.class); } /** * Reads all document collections in a database. * * @param databaseLink the database link. * @param options the fee options. * @return the feed response with the read collections. */ public FeedResponse readCollections(String databaseLink, FeedOptions options) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } logger.debug("Reading Collections. databaseLink: [{}]", databaseLink); String path = Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.DocumentCollection, DocumentCollection.class, path, options)); } /** * Query for document collections in a database. * * @param databaseLink the database link. * @param query the query. * @param options the feed options. * @return the feed response with the obtained collections. */ public FeedResponse queryCollections(String databaseLink, String query, FeedOptions options) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryCollections(databaseLink, new SqlQuerySpec(query, null), options); } /** * Query for document collections in a database. * * @param databaseLink the database link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained collections. */ public FeedResponse queryCollections(String databaseLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Collections. databaseLink: [{}], querySpec [{}]", databaseLink, querySpec); String path = Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.DocumentCollection, DocumentCollection.class, path, querySpec, options)); } /** * Creates a document. * * @param collectionLink the link to the parent document collection. * @param document the document represented as a POJO or Document object. * @param options the request options. * @param disableAutomaticIdGeneration the flag for disabling automatic id generation. * @return the resource response with the created document. * @throws DocumentClientException the document client exception. */ public ResourceResponse createDocument(String collectionLink, Object document, RequestOptions options, boolean disableAutomaticIdGeneration) throws DocumentClientException { logger.debug("Creating a Document. collectionLink: [{}]", collectionLink); final String documentCollectionLink = this.getTargetDocumentCollectionLink(collectionLink, document); final Object documentLocal = document; final RequestOptions optionsLocal = options; final boolean disableAutomaticIdGenerationLocal = disableAutomaticIdGeneration; final boolean shouldRetry = options == null || options.getPartitionKey() == null; RetryCreateDocumentDelegate createDelegate = new RetryCreateDocumentDelegate() { @Override public ResourceResponse apply() throws DocumentClientException { DocumentServiceRequest request = getCreateDocumentRequest(documentCollectionLink, documentLocal, optionsLocal, disableAutomaticIdGenerationLocal, OperationType.Create); return new ResourceResponse(doCreate(request), Document.class); } }; return shouldRetry ? RetryUtility.executeCreateDocument(createDelegate, this.collectionCache, documentCollectionLink) : createDelegate.apply(); } /** * Upserts a document. * * @param collectionLink the link to the parent document collection. * @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 { logger.debug("Upserting a Document. collectionLink: [{}]", collectionLink); final String documentCollectionLink = this.getTargetDocumentCollectionLink(collectionLink, document); final Object documentLocal = document; final RequestOptions optionsLocal = options; final boolean disableAutomaticIdGenerationLocal = disableAutomaticIdGeneration; final boolean shouldRetry = options == null || options.getPartitionKey() == null; RetryCreateDocumentDelegate upsertDelegate = new RetryCreateDocumentDelegate() { @Override public ResourceResponse apply() throws DocumentClientException { DocumentServiceRequest request = getCreateDocumentRequest(documentCollectionLink, documentLocal, optionsLocal, disableAutomaticIdGenerationLocal, OperationType.Upsert); return new ResourceResponse(doUpsert(request), Document.class); } }; return shouldRetry ? RetryUtility.executeCreateDocument(upsertDelegate, this.collectionCache, documentCollectionLink) : upsertDelegate.apply(); } @SuppressWarnings("deprecation") private String getTargetDocumentCollectionLink(String collectionLink, Object document) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (document == null) { throw new IllegalArgumentException("document"); } String documentCollectionLink = collectionLink; if (Utils.isDatabaseLink(collectionLink)) { // Gets the partition resolver(if it exists) for the specified database link PartitionResolver partitionResolver = this.getPartitionResolver(collectionLink); // If the partition resolver exists, get the collection to which the Create/Upsert should be directed using the partition key if (partitionResolver != null) { documentCollectionLink = partitionResolver.resolveForCreate(document); } else { throw new IllegalArgumentException(PartitionResolverErrorMessage); } } return documentCollectionLink; } private DocumentServiceRequest getCreateDocumentRequest(String documentCollectionLink, Object document, RequestOptions options, boolean disableAutomaticIdGeneration, OperationType operationType) { if (StringUtils.isEmpty(documentCollectionLink)) { throw new IllegalArgumentException("documentCollectionLink"); } if (document == null) { throw new IllegalArgumentException("document"); } Document typedDocument = Document.FromObject(document, objectMapper); DocumentClient.validateResource(typedDocument); if (typedDocument.getId() == null && !disableAutomaticIdGeneration) { // We are supposed to use GUID. Basically UUID is the same as GUID // when represented as a string. typedDocument.setId(Utils.getSecureRandomUUID().toString()); } String path = Utils.joinPath(documentCollectionLink, Paths.DOCUMENTS_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.Document, path, typedDocument, requestHeaders); this.addPartitionKeyInformation(request, typedDocument, options); return request; } /** * Replaces a document using a POJO object. * * @param documentLink the document link. * @param document the document represented as a POJO or Document object. * @param options the request options. * @return the resource response with the replaced document. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceDocument(String documentLink, Object document, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } if (document == null) { throw new IllegalArgumentException("document"); } Document typedDocument = Document.FromObject(document, objectMapper); return this.replaceDocumentInternal(documentLink, typedDocument, options); } /** * Replaces a document with the passed in document. * * @param document the document to replace (containing the document id). * @param options the request options. * @return the resource response with the replaced document. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceDocument(Document document, RequestOptions options) throws DocumentClientException { if (document == null) { throw new IllegalArgumentException("document"); } return this.replaceDocumentInternal(document.getSelfLink(), document, options); } private ResourceResponse replaceDocumentInternal(String documentLink, Document document, RequestOptions options) throws DocumentClientException { if (document == null) { throw new IllegalArgumentException("document"); } logger.debug("Replacing a Document. documentLink: [{}]", documentLink); final String documentCollectionName = Utils.getCollectionName(documentLink); final String documentCollectionLink = this.getTargetDocumentCollectionLink(documentCollectionName, document); final String path = Utils.joinPath(documentLink, null); final Map requestHeaders = getRequestHeaders(options); final DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.Document, path, document, requestHeaders); this.addPartitionKeyInformation(request, document, options); final boolean shouldRetry = options == null || options.getPartitionKey() == null; DocumentClient.validateResource(document); RetryCreateDocumentDelegate replaceDelegate = new RetryCreateDocumentDelegate() { @Override public ResourceResponse apply() throws DocumentClientException { return new ResourceResponse(doReplace(request), Document.class); } }; return shouldRetry ? RetryUtility.executeCreateDocument(replaceDelegate, this.collectionCache, documentCollectionLink) : replaceDelegate.apply(); } /** * Deletes a document by the document link. * * @param documentLink the document link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteDocument(String documentLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } logger.debug("Deleting a Document. documentLink: [{}]", documentLink); String path = Utils.joinPath(documentLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.Document, path, requestHeaders); this.addPartitionKeyInformation(request, null, options); return new ResourceResponse(this.doDelete(request), Document.class); } /** * Reads a document by the document link. * * @param documentLink the document link. * @param options the request options. * @return the resource response with the read document. * @throws DocumentClientException the document client exception. */ public ResourceResponse readDocument(String documentLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } logger.debug("Reading a Document. documentLink: [{}]", documentLink); String path = Utils.joinPath(documentLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Document, path, requestHeaders); this.addPartitionKeyInformation(request, null, options); return new ResourceResponse(this.doRead(request), Document.class); } /** * Reads all documents in a document collection. * * @param collectionLink the collection link. * @param options the feed options. * @return the feed response with read documents. */ public FeedResponse readDocuments(String collectionLink, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.debug("Reading Documents. collectionLink: [{}]", collectionLink); String path = Utils.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Document, Document.class, path, options)); } /** * Query for documents in a document collection. * * @param collectionLink the link to the parent document collection. * @param query the query. * @param options the feed options. * @return the feed response with the obtained documents. */ public FeedResponse queryDocuments(String collectionLink, String query, FeedOptions options) { return this.queryDocuments(collectionLink, query, options, null); } /** * Query for documents in a document collection with a partitionKey * * @param collectionLink the link to the parent document collection. * @param query the query. * @param options the feed options. * @param partitionKey the partitionKey. * @return the feed response with the obtained documents. */ public FeedResponse queryDocuments(String collectionLink, String query, FeedOptions options, Object partitionKey) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryDocuments(collectionLink, new SqlQuerySpec(query, null), options, partitionKey); } /** * Query for documents in a document collection. * * @param collectionLink the link to the parent document collection. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained documents. */ public FeedResponse queryDocuments(String collectionLink, SqlQuerySpec querySpec, FeedOptions options) { return this.queryDocuments(collectionLink, querySpec, options, null); } /** * Query for documents in a document collection. * * @param collectionLink the link to the parent document collection. * @param querySpec the SQL query specification. * @param options the feed options. * @param partitionKey the partitionKey. * @return the feed response with the obtained documents. */ public FeedResponse queryDocuments(String collectionLink, SqlQuerySpec querySpec, FeedOptions options, Object partitionKey) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Documents. collectionLink: [{}], querySpec [{}]", collectionLink, querySpec); String path; if (Utils.isDatabaseLink(collectionLink)) { path = collectionLink; } else { path = Utils.joinPath(collectionLink, Paths.DOCUMENTS_PATH_SEGMENT); } return new FeedResponse(new QueryIterable(this, ResourceType.Document, Document.class, path, querySpec, options, partitionKey)); } /** * Query for aggregation values in a document collection. * * @param collectionLink the link to the parent document collection. * @param query the query string. * @param feedOptions the feed options. * @return a list containing the aggregation values */ public List queryAggregateValues(String collectionLink, String query, FeedOptions feedOptions) { return this.queryAggregateValues(collectionLink, new SqlQuerySpec(query, null), feedOptions); } /** * Query for aggregation values in a document collection. * * @param collectionLink the link to the parent document collection. * @param query the query string. * @param feedOptions the feed options. * @param partitionKey the partitionKey. * @return a list containing the aggregation values */ public List queryAggregateValues(String collectionLink, String query, FeedOptions feedOptions, String partitionKey) { return this.queryAggregateValues(collectionLink, new SqlQuerySpec(query, null), feedOptions, partitionKey); } /** * Query for aggregation values in a document collection. * * @param collectionLink the link to the parent document collection. * @param querySpec the SQL query specification. * @param feedOptions the feed options. * @return a list containing the aggregation values */ public List queryAggregateValues(String collectionLink, SqlQuerySpec querySpec, FeedOptions feedOptions) { return this.queryAggregateValues(collectionLink, querySpec, feedOptions, null); } /** * Query for aggregation values in a document collection. * * @param collectionLink the link to the parent document collection. * @param querySpec the SQL query specification. * @param feedOptions the feed options. * @param partitionKey the partitionKey. * @return a list containing the aggregation values */ public List queryAggregateValues(String collectionLink, SqlQuerySpec querySpec, FeedOptions feedOptions, Object partitionKey) { List items = this.queryDocuments(collectionLink, querySpec, feedOptions, partitionKey) .getQueryIterable().toList(); List values = new ArrayList<>(items.size()); for (Document item : items) { if (item.propertyBag.length() > 0) { values.add(item.propertyBag.get(item.propertyBag.keys().next().toString())); } else { values.add(null); } } return values; } /** * Query for documents change feed in a document collection. *

* Example: *

     * String partitionKeyRangeId = "0";   // Use client.readPartitionKeyRanges() to obtain the ranges.
     * String checkpointContinuation = null;
     * ChangeFeedOptions options = new ChangeFeedOptions();
     * options.setPartitionKeyRangeId(partitionKeyRangeId);
     * options.setRequestContinuation(checkpointContinuation);
     * options.setStartFromBeginning(true);
     * FeedResponse<Document> query = client.queryDocumentChangeFeed(coll.getSelfLink(), options);
     * do {
     *     List<Document> docs = query.getQueryIterable().fetchNextBlock();
     *     // Process the documents
     *     // Checkpoint query.getResponseContinuation()
     * } while (query.getQueryIterator().hasNext());
     * 
* @param collectionLink the link to the parent document collection * @param changeFeedOptions the options for processing the query results feed * @return the feed response with the obtained documents * @see ChangeFeedOptions * @see PartitionKeyRange */ public FeedResponse queryDocumentChangeFeed(String collectionLink, ChangeFeedOptions changeFeedOptions) { return this.queryChangeFeed(collectionLink, ResourceType.Document, Document.class, changeFeedOptions); } /** * Query for change feed in a document collection * * @param collectionLink the link to the parent document collection * @param resourceType the ResourceType * @param type the class type of the resource * @param changeFeedOptions the options for processing the query results feed * @param the class type of the resource * @return the change feed response for the provided resource type * @see ChangeFeedOptions * @see PartitionKeyRange */ FeedResponse queryChangeFeed(String collectionLink, ResourceType resourceType, Class type, ChangeFeedOptions changeFeedOptions) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (changeFeedOptions == null) { changeFeedOptions = new ChangeFeedOptions(); } logger.debug("Querying {} change feed. collectionLink: [{}]", resourceType, collectionLink); String path = Utils.joinPath(collectionLink, Utils.getResourceSegement(resourceType)); return new FeedResponse(new QueryIterable(this, resourceType, type, path, changeFeedOptions), true); } /** * Creates a stored procedure. * * @param collectionLink the collection link. * @param storedProcedure the stored procedure to create. * @param options the request options. * @return the resource response with the created stored procedure. * @throws DocumentClientException the document client exception. */ public ResourceResponse createStoredProcedure(String collectionLink, StoredProcedure storedProcedure, RequestOptions options) throws DocumentClientException { logger.debug("Creating a StoredProcedure. collectionLink: [{}], storedProcedure id [{}]", collectionLink, storedProcedure.getId()); DocumentServiceRequest request = getStoredProcedureRequest(collectionLink, storedProcedure, options, OperationType.Create); 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 { logger.debug("Upserting a StoredProcedure. collectionLink: [{}], storedProcedure id [{}]", collectionLink, storedProcedure.getId()); DocumentServiceRequest request = getStoredProcedureRequest(collectionLink, storedProcedure, options, OperationType.Upsert); return new ResourceResponse(this.doUpsert(request), StoredProcedure.class); } private DocumentServiceRequest getStoredProcedureRequest(String collectionLink, StoredProcedure storedProcedure, RequestOptions options, OperationType operationType) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (storedProcedure == null) { throw new IllegalArgumentException("storedProcedure"); } DocumentClient.validateResource(storedProcedure); String path = Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.StoredProcedure, path, storedProcedure, requestHeaders); return request; } /** * Replaces a stored procedure. * * @param storedProcedure the stored procedure to use. * @param options the request options. * @return the resource response with the replaced stored procedure. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceStoredProcedure(StoredProcedure storedProcedure, RequestOptions options) throws DocumentClientException { if (storedProcedure == null) { throw new IllegalArgumentException("storedProcedure"); } logger.debug("Replacing a StoredProcedure. storedProcedure id [{}]", storedProcedure.getId()); DocumentClient.validateResource(storedProcedure); String path = Utils.joinPath(storedProcedure.getSelfLink(), null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.StoredProcedure, path, storedProcedure, requestHeaders); return new ResourceResponse(this.doReplace(request), StoredProcedure.class); } /** * Deletes a stored procedure by the stored procedure link. * * @param storedProcedureLink the stored procedure link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteStoredProcedure(String storedProcedureLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(storedProcedureLink)) { throw new IllegalArgumentException("storedProcedureLink"); } logger.debug("Deleting a StoredProcedure. storedProcedureLink [{}]", storedProcedureLink); String path = Utils.joinPath(storedProcedureLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.StoredProcedure, path, requestHeaders); return new ResourceResponse(this.doDelete(request), StoredProcedure.class); } /** * Read a stored procedure by the stored procedure link. * * @param storedProcedureLink the stored procedure link. * @param options the request options. * @return the resource response with the read stored procedure. * @throws DocumentClientException the document client exception. */ public ResourceResponse readStoredProcedure(String storedProcedureLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(storedProcedureLink)) { throw new IllegalArgumentException("storedProcedureLink"); } logger.debug("Reading a StoredProcedure. storedProcedureLink [{}]", storedProcedureLink); String path = Utils.joinPath(storedProcedureLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.StoredProcedure, path, requestHeaders); return new ResourceResponse(this.doRead(request), StoredProcedure.class); } /** * Reads all stored procedures in a document collection link. * * @param collectionLink the collection link. * @param options the feed options. * @return the feed response with the read stored procedure. */ public FeedResponse readStoredProcedures(String collectionLink, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.debug("Reading StoredProcedures. collectionLink [{}]", collectionLink); String path = Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.StoredProcedure, StoredProcedure.class, path, options)); } /** * Query for stored procedures in a document collection. * * @param collectionLink the collection link. * @param query the query. * @param options the feed options. * @return the feed response with the obtained stored procedures. */ public FeedResponse queryStoredProcedures(String collectionLink, String query, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryStoredProcedures(collectionLink, new SqlQuerySpec(query, null), options); } /** * Query for stored procedures in a document collection. * * @param collectionLink the collection link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained stored procedures. */ public FeedResponse queryStoredProcedures(String collectionLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying StoredProcedures. collectionLink [{}], querySpec [{}]", collectionLink, querySpec); String path = Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.StoredProcedure, StoredProcedure.class, path, querySpec, options)); } /** * Executes a stored procedure by the stored procedure link. * * @param storedProcedureLink the stored procedure link. * @param procedureParams the array of procedure parameter values. * @return the stored procedure response. * @throws DocumentClientException the document client exception. */ public StoredProcedureResponse executeStoredProcedure(String storedProcedureLink, Object[] procedureParams) throws DocumentClientException { return this.executeStoredProcedure(storedProcedureLink, null, procedureParams); } /** * Executes a stored procedure by the stored procedure link. * * @param storedProcedureLink the stored procedure link. * @param options the request options. * @param procedureParams the array of procedure parameter values. * @return the stored procedure response. * @throws DocumentClientException the document client exception. */ public StoredProcedureResponse executeStoredProcedure(String storedProcedureLink, RequestOptions options, Object[] procedureParams) throws DocumentClientException { logger.debug("Executing a StoredProcedure. storedProcedureLink [{}]", storedProcedureLink); String path = Utils.joinPath(storedProcedureLink, null); Map requestHeaders = getRequestHeaders(options); requestHeaders.put(HttpConstants.HttpHeaders.ACCEPT, RuntimeConstants.MediaTypes.JSON); if (options != null) { if (options.getPartitionKey() != null) { requestHeaders.put(HttpConstants.HttpHeaders.PARTITION_KEY, options.getPartitionKey().toString()); } if (options.getPartitionKeyRangeId() != null) { requestHeaders.put(HttpConstants.HttpHeaders.PARTITION_KEY_RANGE_ID, options.getPartitionKeyRangeId()); } if (options.isScriptLoggingEnabled()) { requestHeaders.put(HttpConstants.HttpHeaders.SCRIPT_ENABLE_LOGGING, String.valueOf(true)); } } DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.ExecuteJavaScript, ResourceType.StoredProcedure, path, procedureParams != null ? DocumentClient.serializeProcedureParams(procedureParams, objectMapper) : "", requestHeaders); if (options != null && options.getPartitionKeyRangeId() != null) { PartitionKeyRangeIdentity partitionKeyRangeId = new PartitionKeyRangeIdentity(options.getPartitionKeyRangeId()); request.setPartitionKeyRangeIdentity(partitionKeyRangeId); BridgeInternal.setUserProvidedPartitionKeyRangeIdentity(request, true); } else { this.addPartitionKeyInformation(request, null, options); } return new StoredProcedureResponse(this.doCreate(request)); } /** * Creates a trigger. * * @param collectionLink the collection link. * @param trigger the trigger. * @param options the request options. * @return the resource response with the created trigger. * @throws DocumentClientException the document client exception. */ public ResourceResponse createTrigger(String collectionLink, Trigger trigger, RequestOptions options) throws DocumentClientException { logger.debug("Creating a Trigger. collectionLink [{}], trigger id [{}]", collectionLink, trigger.getId()); DocumentServiceRequest request = getTriggerRequest(collectionLink, trigger, options, OperationType.Create); 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 { logger.debug("Upserting a Trigger. collectionLink [{}], trigger id [{}]", collectionLink, trigger.getId()); DocumentServiceRequest request = getTriggerRequest(collectionLink, trigger, options, OperationType.Upsert); return new ResourceResponse(this.doUpsert(request), Trigger.class); } private DocumentServiceRequest getTriggerRequest(String collectionLink, Trigger trigger, RequestOptions options, OperationType operationType) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (trigger == null) { throw new IllegalArgumentException("trigger"); } DocumentClient.validateResource(trigger); String path = Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.Trigger, path, trigger, requestHeaders); return request; } /** * Replaces a trigger. * * @param trigger the trigger to use. * @param options the request options. * @return the resource response with the replaced trigger. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceTrigger(Trigger trigger, RequestOptions options) throws DocumentClientException { if (trigger == null) { throw new IllegalArgumentException("trigger"); } logger.debug("Replacing a Trigger. trigger id [{}]", trigger.getId()); DocumentClient.validateResource(trigger); String path = Utils.joinPath(trigger.getSelfLink(), null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.Trigger, path, trigger, requestHeaders); return new ResourceResponse(this.doReplace(request), Trigger.class); } /** * Deletes a trigger. * * @param triggerLink the trigger link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteTrigger(String triggerLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(triggerLink)) { throw new IllegalArgumentException("triggerLink"); } logger.debug("Deleting a Trigger. triggerLink [{}]", triggerLink); String path = Utils.joinPath(triggerLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.Trigger, path, requestHeaders); return new ResourceResponse(this.doDelete(request), Trigger.class); } /** * Reads a trigger by the trigger link. * * @param triggerLink the trigger link. * @param options the request options. * @return the resource response with the read trigger. * @throws DocumentClientException the document client exception. */ public ResourceResponse readTrigger(String triggerLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(triggerLink)) { throw new IllegalArgumentException("triggerLink"); } logger.debug("Reading a Trigger. triggerLink [{}]", triggerLink); String path = Utils.joinPath(triggerLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Trigger, path, requestHeaders); return new ResourceResponse(this.doRead(request), Trigger.class); } /** * Reads all triggers in a document collection. * * @param collectionLink the collection link. * @param options the feed options. * @return the feed response with the read triggers. */ public FeedResponse readTriggers(String collectionLink, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.debug("Reading Triggers. collectionLink [{}]", collectionLink); String path = Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); return new FeedResponse( new QueryIterable(this, ResourceType.Trigger, Trigger.class, path, options)); } /** * Query for triggers. * * @param collectionLink the collection link. * @param query the query. * @param options the feed options. * @return the feed response with the obtained triggers. */ public FeedResponse queryTriggers(String collectionLink, String query, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryTriggers(collectionLink, new SqlQuerySpec(query, null), options); } /** * Query for triggers. * * @param collectionLink the collection link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained triggers. */ public FeedResponse queryTriggers(String collectionLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Triggers. collectionLink [{}], querySpec [{}]", collectionLink, querySpec); String path = Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Trigger, Trigger.class, path, querySpec, options)); } /** * Reads all partition key ranges in a document collection. *

* Example: *

     * FeedResponse<PartitionKeyRange> partitionKeyRanges = this.client.readPartitionKeyRanges(coll, null);
     * List<String> ids = new ArrayList<String>();
     * for (PartitionKeyRange range : partitionKeyRanges.getQueryIterable()) {
     *     ids.add(range.getId());
     * }
     * 
* @param collectionLink the collection link. * @param options the feed options. * @return the feed response with the read partition key ranges. * @see PartitionKeyRange */ public FeedResponse readPartitionKeyRanges(String collectionLink, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.trace("Reading PartitionKeyRanges. collectionLink [{}]", collectionLink); String path = collectionLink.endsWith(Paths.PARTITION_KEY_RANGE_PATH_SEGMENT) ? collectionLink : Utils.joinPath(collectionLink, Paths.PARTITION_KEY_RANGE_PATH_SEGMENT); if (!path.startsWith("/")) { path = "/" + path; } return new FeedResponse(new QueryIterable(this, ResourceType.PartitionKeyRange, PartitionKeyRange.class, path, options)); } /** * Reads all partition key ranges in a document collection. *

* Example: *

     * FeedResponse<PartitionKeyRange> partitionKeyRanges = this.client.readPartitionKeyRanges(coll, null);
     * List<String> ids = new ArrayList<String>();
     * for (PartitionKeyRange range : partitionKeyRanges.getQueryIterable()) {
     *     ids.add(range.getId());
     * }
     * 
* @param documentCollection the document collection. * @param options the feed options. * @return the feed response with the read partition key ranges. * @see PartitionKeyRange */ public FeedResponse readPartitionKeyRanges(DocumentCollection documentCollection, FeedOptions options) { if (documentCollection == null) { throw new IllegalArgumentException("collection"); } return this.readPartitionKeyRanges(documentCollection.getSelfLink(), options); } /** * Reads all partition key ranges in a document collection for a provided query. * * @param collectionLink the document collection link * @param query the query string * @return the list of the serving partition key ranges for the query * @throws DocumentClientException from partition key ranges query */ public Collection readPartitionKeyRanges(String collectionLink, String query) throws DocumentClientException { String path = collectionLink; DocumentCollection collection = this.collectionCache.resolveByName(collectionLink); if (ServiceJNIWrapper.isServiceJNIAvailable()) { QueryPartitionProvider queryPartitionProvider = new QueryPartitionProvider(this .getDatabaseAccountConfigurationProvider() .getQueryEngineConfiguration()); PartitionedQueryExecutionInfo partitionedQueryExecutionInfo = queryPartitionProvider.getPartitionQueryExcecutionInfo(new SqlQuerySpec(query), collection.getPartitionKey()); Collection partitionKeyRanges = RoutingMapProviderHelper.getOverlappingRanges( this.getPartitionKeyRangeCache(), path, partitionedQueryExecutionInfo.getQueryRanges()); return partitionKeyRanges; } if (!path.startsWith("/")) { path = "/" + path; } SqlQuerySpec querySpec = new SqlQuerySpec(query, new SqlParameterCollection()); FeedOptions options = new FeedOptions(); options.setEnableCrossPartitionQuery(true); options.setMaxDegreeOfParallelism(Integer.MAX_VALUE); HashMap headers = new HashMap<>(); headers.put(HttpConstants.HttpHeaders.ENABLE_CROSS_PARTITION_QUERY, String.valueOf(true)); headers.put(HttpConstants.HttpHeaders.PARALLELIZE_CROSS_PARTITION_QUERY, String.valueOf(true)); headers.put(HttpConstants.HttpHeaders.PAGE_SIZE, String.valueOf(1)); Collection partitionKeyRanges = new ArrayList<>(); try { DocumentServiceRequest request = DocumentServiceRequest.create( ResourceType.Document, Utils.joinPath(path, Paths.DOCUMENTS_PATH_SEGMENT), querySpec, QueryCompatibilityMode.Default, headers); if (!Utils.isCollectionPartitioned(collection)) { request.routeTo(new PartitionKeyRangeIdentity(collection.getResourceId(), "0")); } DocumentServiceResponse response = this.doQuery(request); String sessionToken = response.getResponseHeaders().get(HttpConstants.HttpHeaders.SESSION_TOKEN); if (!sessionToken.isEmpty()) { String[] parts = StringUtils.split(sessionToken,':'); if (parts.length == 2) { PartitionKeyRange singleRange = new PartitionKeyRange(); singleRange.setId(parts[0]); partitionKeyRanges.add(singleRange); } } if (partitionKeyRanges.size() == 0) { // Partition key ranges information not returned from Gateway partitionKeyRanges = this.readPartitionKeyRanges(collectionLink, (FeedOptions) null).getQueryIterable().toList(); } } catch (DocumentClientException dce) { if (dce.getError() == null) { // This exception is unexpected, it's likely an issue with the query throw dce; } String partitionedQueryExecutionInfoJsonString = dce.getError().getPartitionedQueryExecutionInfo(); if(partitionedQueryExecutionInfoJsonString == null) { // If the info isn't there, we can't proceed and should throw the error. throw dce; } PartitionedQueryExecutionInfo partitionedQueryExecutionInfo = new PartitionedQueryExecutionInfo(partitionedQueryExecutionInfoJsonString); partitionKeyRanges = RoutingMapProviderHelper.getOverlappingRanges( this.getPartitionKeyRangeCache(), path, partitionedQueryExecutionInfo.getQueryRanges()); } return partitionKeyRanges; } /** * Creates a user defined function. * * @param collectionLink the collection link. * @param udf the user defined function. * @param options the request options. * @return the resource response with the created user defined function. * @throws DocumentClientException the document client exception. */ public ResourceResponse createUserDefinedFunction( String collectionLink, UserDefinedFunction udf, RequestOptions options) throws DocumentClientException { logger.debug("Creating a UserDefinedFunction. collectionLink [{}], udf id [{}]", collectionLink, udf.getId()); DocumentServiceRequest request = getUserDefinedFunctionRequest(collectionLink, udf, options, OperationType.Create); 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 { logger.debug("Upserting a UserDefinedFunction. collectionLink [{}], udf id [{}]", collectionLink, udf.getId()); DocumentServiceRequest request = getUserDefinedFunctionRequest(collectionLink, udf, options, OperationType.Upsert); return new ResourceResponse(this.doUpsert(request), UserDefinedFunction.class); } private DocumentServiceRequest getUserDefinedFunctionRequest(String collectionLink, UserDefinedFunction udf, RequestOptions options, OperationType operationType) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (udf == null) { throw new IllegalArgumentException("udf"); } DocumentClient.validateResource(udf); String path = Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.UserDefinedFunction, path, udf, requestHeaders); return request; } /** * Replaces a user defined function. * * @param udf the user defined function. * @param options the request options. * @return the resource response with the replaced user defined function. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceUserDefinedFunction(UserDefinedFunction udf, RequestOptions options) throws DocumentClientException { if (udf == null) { throw new IllegalArgumentException("udf"); } logger.debug("Replacing a UserDefinedFunction. udf id [{}]", udf.getId()); DocumentClient.validateResource(udf); String path = Utils.joinPath(udf.getSelfLink(), null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.UserDefinedFunction, path, udf, requestHeaders); return new ResourceResponse(this.doReplace(request), UserDefinedFunction.class); } /** * Deletes a user defined function. * * @param udfLink the user defined function link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteUserDefinedFunction(String udfLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(udfLink)) { throw new IllegalArgumentException("udfLink"); } logger.debug("Deleting a UserDefinedFunction. udfLink [{}]", udfLink); String path = Utils.joinPath(udfLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.UserDefinedFunction, path, requestHeaders); return new ResourceResponse(this.doDelete(request), UserDefinedFunction.class); } /** * Read a user defined function. * * @param udfLink the user defined function link. * @param options the request options. * @return the resource response with the read user defined function. * @throws DocumentClientException the document client exception. */ public ResourceResponse readUserDefinedFunction(String udfLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(udfLink)) { throw new IllegalArgumentException("udfLink"); } logger.debug("Reading a UserDefinedFunction. udfLink [{}]", udfLink); String path = Utils.joinPath(udfLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.UserDefinedFunction, path, requestHeaders); return new ResourceResponse(this.doRead(request), UserDefinedFunction.class); } /** * Reads all user defined functions in a document collection. * * @param collectionLink the collection link. * @param options the feed options. * @return the feed response with the read user defined functions. */ public FeedResponse readUserDefinedFunctions(String collectionLink, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.debug("Reading UserDefinedFunctions. collectionLink [{}]", collectionLink); String path = Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.UserDefinedFunction, UserDefinedFunction.class, path, options)); } /** * Query for user defined functions. * * @param collectionLink the collection link. * @param query the query. * @param options the feed options. * @return the feed response with the obtained user defined functions. */ public FeedResponse queryUserDefinedFunctions(String collectionLink, String query, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryUserDefinedFunctions(collectionLink, new SqlQuerySpec(query, null), options); } /** * Query for user defined functions. * * @param collectionLink the collection link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained user defined functions. */ public FeedResponse queryUserDefinedFunctions(String collectionLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying UserDefinedFunctions. collectionLink [{}], querySpec [{}]", collectionLink, querySpec); String path = Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.UserDefinedFunction, UserDefinedFunction.class, path, querySpec, options)); } /** * Creates an attachment. * * @param documentLink the document link. * @param attachment the attachment to create. * @param options the request options. * @return the resource response with the created attachment. * @throws DocumentClientException the document client exception. */ public ResourceResponse createAttachment(String documentLink, Attachment attachment, RequestOptions options) throws DocumentClientException { logger.debug("Creating a Attachment. documentLink [{}], attachment id [{}]", documentLink, attachment.getId()); DocumentServiceRequest request = getAttachmentRequest(documentLink, attachment, options, OperationType.Create); 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 { logger.debug("Upserting a Attachment. documentLink [{}], attachment id [{}]", documentLink, attachment.getId()); DocumentServiceRequest request = getAttachmentRequest(documentLink, attachment, options, OperationType.Upsert); return new ResourceResponse(this.doUpsert(request), Attachment.class); } private DocumentServiceRequest getAttachmentRequest(String documentLink, Attachment attachment, RequestOptions options, OperationType operationType) { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } if (attachment == null) { throw new IllegalArgumentException("attachment"); } DocumentClient.validateResource(attachment); String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.Attachment, path, attachment, requestHeaders); this.addPartitionKeyInformation(request, null, options); return request; } /** * Replaces an attachment. * * @param attachment the attachment to use. * @param options the request options. * @return the resource response with the replaced attachment. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceAttachment(Attachment attachment, RequestOptions options) throws DocumentClientException { if (attachment == null) { throw new IllegalArgumentException("attachment"); } logger.debug("Replacing a Attachment. attachment id [{}]", attachment.getId()); DocumentClient.validateResource(attachment); String path = Utils.joinPath(attachment.getSelfLink(), null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.Attachment, path, attachment, requestHeaders); this.addPartitionKeyInformation(request, null, options); return new ResourceResponse(this.doReplace(request), Attachment.class); } /** * Deletes an attachment. * * @param attachmentLink the attachment link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteAttachment(String attachmentLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(attachmentLink)) { throw new IllegalArgumentException("attachmentLink"); } logger.debug("Deleting a Attachment. attachmentLink [{}]", attachmentLink); String path = Utils.joinPath(attachmentLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.Attachment, path, requestHeaders); this.addPartitionKeyInformation(request, null, options); return new ResourceResponse(this.doDelete(request), Attachment.class); } /** * Reads an attachment. * * @param attachmentLink the attachment link. * @param options the request options. * @return the resource response with the read attachment. * @throws DocumentClientException the document client exception. */ public ResourceResponse readAttachment(String attachmentLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(attachmentLink)) { throw new IllegalArgumentException("attachmentLink"); } logger.debug("Reading a Attachment. attachmentLink [{}]", attachmentLink); String path = Utils.joinPath(attachmentLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Attachment, path, requestHeaders); this.addPartitionKeyInformation(request, null, options); return new ResourceResponse(this.doRead(request), Attachment.class); } /** * Reads all attachments in a document. * * @param documentLink the document link. * @param options the feed options. * @return the feed response with the read attachments. */ public FeedResponse readAttachments(String documentLink, FeedOptions options) { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } logger.debug("Reading Attachments. attachmentLink [{}]", documentLink); String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Attachment, Attachment.class, path, options)); } /** * Query for attachments. * * @param documentLink the document link. * @param query the query. * @param options the feed options. * @return the feed response with the obtained attachments. */ public FeedResponse queryAttachments(String documentLink, String query, FeedOptions options) { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryAttachments(documentLink, new SqlQuerySpec(query, null), options); } /** * Query for attachments. * * @param documentLink the document link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained attachments. */ public FeedResponse queryAttachments(String documentLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Attachments. attachmentLink [{}], querySpec [{}]", documentLink, querySpec); String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Attachment, Attachment.class, path, querySpec, options)); } /** * Creates an attachment. * * @param documentLink the document link. * @param mediaStream the media stream for creating the attachment. * @param options the media options. * @return the resource response with the created attachment. * @throws DocumentClientException the document client exception. */ public ResourceResponse createAttachment(String documentLink, InputStream mediaStream, MediaOptions options) throws DocumentClientException { logger.debug("Creating a Attachment. attachmentLink [{}]", documentLink); DocumentServiceRequest request = getAttachmentRequest(documentLink, mediaStream, options, OperationType.Create); 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 { logger.debug("Upserting a Attachment. attachmentLink [{}]", documentLink); DocumentServiceRequest request = getAttachmentRequest(documentLink, mediaStream, options, OperationType.Upsert); return new ResourceResponse(this.doUpsert(request), Attachment.class); } private DocumentServiceRequest getAttachmentRequest(String documentLink, InputStream mediaStream, MediaOptions options, OperationType operationType) { if (StringUtils.isEmpty(documentLink)) { throw new IllegalArgumentException("documentLink"); } if (mediaStream == null) { throw new IllegalArgumentException("mediaStream"); } String path = Utils.joinPath(documentLink, Paths.ATTACHMENTS_PATH_SEGMENT); Map requestHeaders = this.getMediaHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.Attachment, path, mediaStream, requestHeaders); request.setIsMedia(true); this.addPartitionKeyInformation(request, null, null); return request; } /** * Reads a media by the media link. * * @param mediaLink the media link. * @return the media response. * @throws DocumentClientException the document client exception. */ public MediaResponse readMedia(String mediaLink) throws DocumentClientException { if (StringUtils.isEmpty(mediaLink)) { throw new IllegalArgumentException("mediaLink"); } logger.debug("Reading a Media. mediaLink [{}]", mediaLink); String path = Utils.joinPath(mediaLink, null); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Media, path, null); request.setIsMedia(true); return new MediaResponse(this.doRead(request), this.connectionPolicy.getMediaReadMode() == MediaReadMode.Buffered); } /** * Updates a media by the media link. * * @param mediaLink the media link. * @param mediaStream the media stream to upload. * @param options the media options. * @return the media response. * @throws DocumentClientException the document client exception. */ public MediaResponse updateMedia(String mediaLink, InputStream mediaStream, MediaOptions options) throws DocumentClientException { if (StringUtils.isEmpty(mediaLink)) { throw new IllegalArgumentException("mediaLink"); } if (mediaStream == null) { throw new IllegalArgumentException("mediaStream"); } logger.debug("Updating a Media. mediaLink [{}]", mediaLink); String path = Utils.joinPath(mediaLink, null); Map requestHeaders = this.getMediaHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.Media, path, mediaStream, requestHeaders); request.setIsMedia(true); return new MediaResponse(this.doReplace(request), this.connectionPolicy.getMediaReadMode() == MediaReadMode.Buffered); } /** * Reads a conflict. * * @param conflictLink the conflict link. * @param options the request options. * @return the resource response with the read conflict. * @throws DocumentClientException the document client exception. */ public ResourceResponse readConflict(String conflictLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(conflictLink)) { throw new IllegalArgumentException("conflictLink"); } logger.debug("Reading a Conflict. conflictLink [{}]", conflictLink); String path = Utils.joinPath(conflictLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Conflict, path, requestHeaders); this.addPartitionKeyInformation(request, null, options); return new ResourceResponse(this.doRead(request), Conflict.class); } /** * Reads all conflicts in a document collection. * * @param collectionLink the collection link. * @param options the feed options. * @return the feed response with the read conflicts. */ public FeedResponse readConflicts(String collectionLink, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } logger.debug("Reading Conflicts. collectionLink [{}]", collectionLink); String path = Utils.joinPath(collectionLink, Paths.CONFLICTS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Conflict, Conflict.class, path, options)); } /** * Query for conflicts. * * @param collectionLink the collection link. * @param query the query. * @param options the feed options. * @return the feed response of the obtained conflicts. */ public FeedResponse queryConflicts(String collectionLink, String query, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryConflicts(collectionLink, new SqlQuerySpec(query, null), options); } /** * Query for conflicts. * * @param collectionLink the collection link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response of the obtained conflicts. */ public FeedResponse queryConflicts(String collectionLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(collectionLink)) { throw new IllegalArgumentException("collectionLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Conflicts. collectionLink [{}], querySpec [{}]", collectionLink, querySpec); String path = Utils.joinPath(collectionLink, Paths.CONFLICTS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Conflict, Conflict.class, path, querySpec, options)); } /** * Deletes a conflict. * * @param conflictLink the conflict link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteConflict(String conflictLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(conflictLink)) { throw new IllegalArgumentException("conflictLink"); } logger.debug("Deleting a Conflicts. conflictLink [{}]", conflictLink); String path = Utils.joinPath(conflictLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.Conflict, path, requestHeaders); this.addPartitionKeyInformation(request, null, options); return new ResourceResponse(this.doDelete(request), Conflict.class); } /** * Creates a user. * * @param databaseLink the database link. * @param user the user to create. * @param options the request options. * @return the resource response with the created user. * @throws DocumentClientException the document client exception. */ public ResourceResponse createUser(String databaseLink, User user, RequestOptions options) throws DocumentClientException { logger.debug("Creating a User. databaseLink [{}], user id [{}]", databaseLink, user.getId()); DocumentServiceRequest request = getUserRequest(databaseLink, user, options, OperationType.Create); 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 { logger.debug("Upserting a User. databaseLink [{}], user id [{}]", databaseLink, user.getId()); DocumentServiceRequest request = getUserRequest(databaseLink, user, options, OperationType.Upsert); return new ResourceResponse(this.doUpsert(request), User.class); } private DocumentServiceRequest getUserRequest(String databaseLink, User user, RequestOptions options, OperationType operationType) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } if (user == null) { throw new IllegalArgumentException("user"); } DocumentClient.validateResource(user); String path = Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.User, path, user, requestHeaders); return request; } /** * Replaces a user. * * @param user the user to use. * @param options the request options. * @return the resource response with the replaced user. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceUser(User user, RequestOptions options) throws DocumentClientException { if (user == null) { throw new IllegalArgumentException("user"); } logger.debug("Replacing a User. user id [{}]", user.getId()); DocumentClient.validateResource(user); String path = Utils.joinPath(user.getSelfLink(), null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.User, path, user, requestHeaders); return new ResourceResponse(this.doReplace(request), User.class); } /** * Deletes a user. * * @param userLink the user link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deleteUser(String userLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(userLink)) { throw new IllegalArgumentException("userLink"); } logger.debug("Deleting a User. userLink [{}]", userLink); String path = Utils.joinPath(userLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.User, path, requestHeaders); return new ResourceResponse(this.doDelete(request), User.class); } /** * Reads a user. * * @param userLink the user link. * @param options the request options. * @return the resource response with the read user. * @throws DocumentClientException the document client exception. */ public ResourceResponse readUser(String userLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(userLink)) { throw new IllegalArgumentException("userLink"); } logger.debug("Reading a User. userLink [{}]", userLink); String path = Utils.joinPath(userLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.User, path, requestHeaders); return new ResourceResponse(this.doRead(request), User.class); } /** * Reads all users in a database. * * @param databaseLink the database link. * @param options the feed options. * @return the feed response with the read users. */ public FeedResponse readUsers(String databaseLink, FeedOptions options) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } logger.debug("Reading Users. databaseLink [{}]", databaseLink); String path = Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.User, User.class, path, options)); } /** * Query for users. * * @param databaseLink the database link. * @param query the query. * @param options the feed options. * @return the feed response of the obtained users. */ public FeedResponse queryUsers(String databaseLink, String query, FeedOptions options) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryUsers(databaseLink, new SqlQuerySpec(query, null), options); } /** * Query for users. * * @param databaseLink the database link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response of the obtained users. */ public FeedResponse queryUsers(String databaseLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(databaseLink)) { throw new IllegalArgumentException("databaseLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Users. databaseLink [{}], querySpec [{}]", databaseLink, querySpec); String path = Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.User, User.class, path, querySpec, options)); } /** * Creates a permission. * * @param userLink the user link. * @param permission the permission to create. * @param options the request options. * @return the resource response with the created permission. * @throws DocumentClientException the document client exception. */ public ResourceResponse createPermission(String userLink, Permission permission, RequestOptions options) throws DocumentClientException { logger.debug("Creating a Permission. userLink [{}], permission id [{}]", userLink, permission.getId()); DocumentServiceRequest request = getPermissionRequest(userLink, permission, options, OperationType.Create); 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 { logger.debug("Upserting a Permission. userLink [{}], permission id [{}]", userLink, permission.getId()); DocumentServiceRequest request = getPermissionRequest(userLink, permission, options, OperationType.Upsert); return new ResourceResponse(this.doUpsert(request), Permission.class); } private DocumentServiceRequest getPermissionRequest(String userLink, Permission permission, RequestOptions options, OperationType operationType) { if (StringUtils.isEmpty(userLink)) { throw new IllegalArgumentException("userLink"); } if (permission == null) { throw new IllegalArgumentException("permission"); } DocumentClient.validateResource(permission); String path = Utils.joinPath(userLink, Paths.PERMISSIONS_PATH_SEGMENT); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(operationType, ResourceType.Permission, path, permission, requestHeaders); return request; } /** * Replaces a permission. * * @param permission the permission to use. * @param options the request options. * @return the resource response with the replaced permission. * @throws DocumentClientException the document client exception. */ public ResourceResponse replacePermission(Permission permission, RequestOptions options) throws DocumentClientException { if (permission == null) { throw new IllegalArgumentException("permission"); } logger.debug("Replacing a Permission. permission id [{}]", permission.getId()); DocumentClient.validateResource(permission); String path = Utils.joinPath(permission.getSelfLink(), null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.Permission, path, permission, requestHeaders); return new ResourceResponse(this.doReplace(request), Permission.class); } /** * Deletes a permission. * * @param permissionLink the permission link. * @param options the request options. * @return the resource response. * @throws DocumentClientException the document client exception. */ public ResourceResponse deletePermission(String permissionLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(permissionLink)) { throw new IllegalArgumentException("permissionLink"); } logger.debug("Deleting a Permission. permissionLink [{}]", permissionLink); String path = Utils.joinPath(permissionLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Delete, ResourceType.Permission, path, requestHeaders); return new ResourceResponse(this.doDelete(request), Permission.class); } /** * Reads a permission. * * @param permissionLink the permission link. * @param options the request options. * @return the resource response with the read permission. * @throws DocumentClientException the document client exception. */ public ResourceResponse readPermission(String permissionLink, RequestOptions options) throws DocumentClientException { if (StringUtils.isEmpty(permissionLink)) { throw new IllegalArgumentException("permissionLink"); } logger.debug("Reading a Permission. permissionLink [{}]", permissionLink); String path = Utils.joinPath(permissionLink, null); Map requestHeaders = getRequestHeaders(options); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Permission, path, requestHeaders); return new ResourceResponse(this.doRead(request), Permission.class); } /** * Reads all permissions. * * @param userLink the user link. * @param options the feed options. * @return the feed response with the read permissions. */ public FeedResponse readPermissions(String userLink, FeedOptions options) { if (StringUtils.isEmpty(userLink)) { throw new IllegalArgumentException("permissionLink"); } logger.debug("Reading Permissions. permissionLink [{}]", userLink); String path = Utils.joinPath(userLink, Paths.PERMISSIONS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Permission, Permission.class, path, options)); } /** * Query for permissions. * * @param userLink the user link. * @param query the query. * @param options the feed options. * @return the feed response with the obtained permissions. */ public FeedResponse queryPermissions(String userLink, String query, FeedOptions options) { if (StringUtils.isEmpty(userLink)) { throw new IllegalArgumentException("permissionLink"); } if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryPermissions(userLink, new SqlQuerySpec(query, null), options); } /** * Query for permissions. * * @param permissionLink the permission link. * @param querySpec the SQL query specification. * @param options the feed options. * @return the feed response with the obtained permissions. */ public FeedResponse queryPermissions(String permissionLink, SqlQuerySpec querySpec, FeedOptions options) { if (StringUtils.isEmpty(permissionLink)) { throw new IllegalArgumentException("permissionLink"); } if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Permissions. permissionLink [{}], querySpec [{}]", permissionLink, querySpec); String path = Utils.joinPath(permissionLink, Paths.PERMISSIONS_PATH_SEGMENT); return new FeedResponse(new QueryIterable(this, ResourceType.Permission, Permission.class, path, querySpec, options)); } /** * Replaces an offer. * * @param offer the offer to use. * @return the resource response with the replaced offer. * @throws DocumentClientException the document client exception. */ public ResourceResponse replaceOffer(Offer offer) throws DocumentClientException { if (offer == null) { throw new IllegalArgumentException("offer"); } logger.debug("Replacing an Offer. offer id [{}]", offer.getId()); DocumentClient.validateResource(offer); String path = Utils.joinPath(offer.getSelfLink(), null); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Replace, ResourceType.Offer, path, offer, null); return new ResourceResponse(this.doReplace(request), Offer.class); } /** * Reads an offer. * * @param offerLink the offer link. * @return the resource response with the read offer. * @throws DocumentClientException the document client exception. */ public ResourceResponse readOffer(String offerLink) throws DocumentClientException { if (StringUtils.isEmpty(offerLink)) { throw new IllegalArgumentException("offerLink"); } logger.debug("Reading an Offer. offerLink [{}]", offerLink); String path = Utils.joinPath(offerLink, null); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.Offer, path, null); return new ResourceResponse(this.doRead(request), Offer.class); } /** * Reads offers. * * @param options the feed options. * @return the feed response with the read offers. */ public FeedResponse readOffers(FeedOptions options) { logger.debug("Reading Offers."); String path = Utils.joinPath(Paths.OFFERS_PATH_SEGMENT, null); return new FeedResponse(new QueryIterable(this, ResourceType.Offer, Offer.class, path, options)); } /** * Query for offers in a database. * * @param query the query. * @param options the feed options. * @return the feed response with the obtained offers. */ public FeedResponse queryOffers(String query, FeedOptions options) { if (StringUtils.isEmpty(query)) { throw new IllegalArgumentException("query"); } return queryOffers(new SqlQuerySpec(query, null), options); } /** * Query for offers in a database. * * @param querySpec the query specification. * @param options the feed options. * @return the feed response with the obtained offers. */ public FeedResponse queryOffers(SqlQuerySpec querySpec, FeedOptions options) { if (querySpec == null) { throw new IllegalArgumentException("querySpec"); } logger.debug("Querying Offers. querySpec [{}]", querySpec); String path = Utils.joinPath(Paths.OFFERS_PATH_SEGMENT, null); return new FeedResponse(new QueryIterable(this, ResourceType.Offer, Offer.class, path, querySpec, options)); } /** * Gets database account information. * * @return the database account. * @throws DocumentClientException the document client exception. */ public DatabaseAccount getDatabaseAccount() throws DocumentClientException { logger.debug("Getting Database Account"); DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.DatabaseAccount, "", // path null); DocumentServiceResponse response = this.doRead(request); DatabaseAccount account = response.getResource(DatabaseAccount.class); // read the headers and set to the account Map responseHeader = response.getResponseHeaders(); account.setMaxMediaStorageUsageInMB(Long.valueOf(responseHeader.get( HttpConstants.HttpHeaders.MAX_MEDIA_STORAGE_USAGE_IN_MB))); account.setMediaStorageUsageInMB(Long.valueOf(responseHeader.get( HttpConstants.HttpHeaders.CURRENT_MEDIA_STORAGE_USAGE_IN_MB))); return account; } private DocumentServiceResponse doCreate(DocumentServiceRequest request) throws DocumentClientException { this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.POST); RetryRequestDelegate createDelegate = new RetryRequestDelegate() { @Override public DocumentServiceResponse apply(DocumentServiceRequest requestInner) throws DocumentClientException { StoreModel proxy = getStoreProxy(requestInner); return proxy.processMessage(requestInner); } }; this.applySessionToken(request); DocumentServiceResponse response = RetryUtility.executeDocumentClientRequest(createDelegate, this, this.globalEndpointManager, request, this.collectionCache); this.captureSessionToken(request, response); return response; } private DocumentServiceResponse doUpsert(DocumentServiceRequest request) throws DocumentClientException { this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.POST); RetryRequestDelegate upsertDelegate = new RetryRequestDelegate() { @Override public DocumentServiceResponse apply(DocumentServiceRequest requestInner) throws DocumentClientException { return getStoreProxy(requestInner).processMessage(requestInner); } }; 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 = RetryUtility.executeDocumentClientRequest(upsertDelegate, this, this.globalEndpointManager, request, this.collectionCache); this.captureSessionToken(request, response); return response; } private DocumentServiceResponse doReplace(DocumentServiceRequest request) throws DocumentClientException { this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.PUT); RetryRequestDelegate replaceDelegate = new RetryRequestDelegate() { @Override public DocumentServiceResponse apply(DocumentServiceRequest requestInner) throws DocumentClientException { return getStoreProxy(requestInner).processMessage(requestInner); } }; this.applySessionToken(request); DocumentServiceResponse response = RetryUtility.executeDocumentClientRequest(replaceDelegate, this, this.globalEndpointManager, request, this.collectionCache); this.captureSessionToken(request, response); return response; } private DocumentServiceResponse doDelete(DocumentServiceRequest request) throws DocumentClientException { RetryRequestDelegate deleteDelegate = new RetryRequestDelegate() { @Override public DocumentServiceResponse apply(DocumentServiceRequest requestInner) throws DocumentClientException { return getStoreProxy(requestInner).processMessage(requestInner); } }; this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.DELETE); this.applySessionToken(request); DocumentServiceResponse response = RetryUtility.executeDocumentClientRequest(deleteDelegate, this, this.globalEndpointManager, request, this.collectionCache); this.captureSessionToken(request, response); return response; } private DocumentServiceResponse doRead(DocumentServiceRequest request) throws DocumentClientException { this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.GET); RetryRequestDelegate readDelegate = new RetryRequestDelegate() { @Override public DocumentServiceResponse apply(DocumentServiceRequest requestInner) throws DocumentClientException { return getStoreProxy(requestInner).processMessage(requestInner); } }; this.applySessionToken(request); DocumentServiceResponse response = RetryUtility.executeDocumentClientRequest(readDelegate, this, this.globalEndpointManager, request, this.collectionCache); this.captureSessionToken(request, response); return response; } DocumentServiceResponse doReadFeed(DocumentServiceRequest request) throws DocumentClientException { RetryRequestDelegate readDelegate = new RetryRequestDelegate() { @Override public DocumentServiceResponse apply(DocumentServiceRequest requestInner) throws DocumentClientException { return getStoreProxy(requestInner).processMessage(requestInner); } }; this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.GET); if (!request.isChangeFeedRequest()) { this.applySessionToken(request); } DocumentServiceResponse response = RetryUtility.executeDocumentClientRequest(readDelegate, this, this.globalEndpointManager, request, this.collectionCache); this.captureSessionToken(request, response); return response; } DocumentServiceResponse doQuery(DocumentServiceRequest request) throws DocumentClientException { RetryRequestDelegate readDelegate = new RetryRequestDelegate() { @Override public DocumentServiceResponse apply(DocumentServiceRequest requestInner) throws DocumentClientException { return getStoreProxy(requestInner).processMessage(requestInner); } }; this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.POST); this.applySessionToken(request); DocumentServiceResponse response = RetryUtility.executeDocumentClientRequest(readDelegate, this, this.globalEndpointManager, request, this.collectionCache); this.captureSessionToken(request, response); return response; } // Used by globalEndpointManager DatabaseAccount getDatabaseAccountFromEndpoint(URI endpoint) throws DocumentClientException { DatabaseAccount databaseAccount = getDatabaseAccountFromEndpointPrivate(endpoint); this.useMultipleWriteLocations = this.connectionPolicy.isUsingMultipleWriteLocations() && databaseAccount != null && databaseAccount.getEnableMultipleWritableLocations(); return databaseAccount; } DatabaseAccount getDatabaseAccountFromEndpointPrivate(URI endpoint) throws DocumentClientException { DocumentServiceRequest request = DocumentServiceRequest.create(OperationType.Read, ResourceType.DatabaseAccount, "", null); this.putMoreContentIntoDocumentServiceRequest(request, HttpConstants.HttpMethods.GET); DocumentServiceResponse response = null; try { request.setEndpointOverride(endpoint); response = this.gatewayProxy.doRead(request); } catch (IllegalStateException e) { // Ignore all errors. Discover is an optimization. String message = "Failed to retrieve database account information. %s"; Throwable cause = e.getCause(); if (cause != null) { message = String.format(message, cause.toString()); } else { message = String.format(message, e.toString()); } logger.warn(message); } if (response != null) { return response.getResource(DatabaseAccount.class); } else { return null; } } /* * This method needs to be at package visibility for the session tests. */ void applySessionToken(DocumentServiceRequest request) throws DocumentClientException { // For master resources skips setting session token. // // For read/query requests, resolves target partition key range and using that sets local session token. // If there is a cached session token associated with the resolved partition will use that otherwise will try to // find session token associated with a parent partition (if any). // // For user provided session token passes, scopes the session token down to the target partition if possible. // // If the request doesn't target a specific partition uses the combined session token. String consistency = StringUtils.defaultString( request.getHeaders().get(HttpConstants.HttpHeaders.CONSISTENCY_LEVEL), desiredConsistencyLevel != null? desiredConsistencyLevel.name():null); boolean isSessionConsistency = ConsistencyLevel.Session.name().equals(consistency); if (!isSessionConsistency || request.getResourceType().isMasterResource()) { request.getHeaders().remove(HttpConstants.HttpHeaders.SESSION_TOKEN); return; // Only apply the session token in case of session consistency and when resource is not a master resource } Map headers = request.getHeaders(); if (headers != null && !StringUtils.isEmpty(headers.get(HttpConstants.HttpHeaders.SESSION_TOKEN))) { if (request.getResourceType().isMasterResource()) { headers.remove(HttpConstants.HttpHeaders.SESSION_TOKEN); return; } } // Apply the ambient session. if (!StringUtils.isEmpty(request.getResourceAddress())) { if (request.isReadOnlyRequest()) { String sessionToken = this.sessionContainer.resolveSessionToken(request); if (!StringUtils.isEmpty(sessionToken)) { headers.put(HttpConstants.HttpHeaders.SESSION_TOKEN, sessionToken); } } } } /* * This method needs to be at package visibility for the session tests. */ void captureSessionToken(DocumentServiceRequest request, DocumentServiceResponse response) throws DocumentClientException { SessionTokenHelper.captureSessionToken(this.sessionContainer, request, response.getResponseHeaders()); } Map getRequestHeaders(RequestOptions options) { Map headers = new HashMap(); if (this.useMultipleWriteLocations) { headers.put(HttpConstants.HttpHeaders.ALLOW_TENTATIVE_WRITES, Boolean.toString(true)); } if (options == null) return headers; Map customOptions = options.getCustomRequestOptions(); if (customOptions != null) { headers.putAll(customOptions); } if (options.getAccessCondition() != null) { if (options.getAccessCondition().getType() == AccessConditionType.IfMatch) { headers.put(HttpConstants.HttpHeaders.IF_MATCH, options.getAccessCondition().getCondition()); } else { headers.put(HttpConstants.HttpHeaders.IF_NONE_MATCH, options.getAccessCondition().getCondition()); } } if (options.getConsistencyLevel() != null) { headers.put(HttpConstants.HttpHeaders.CONSISTENCY_LEVEL, options.getConsistencyLevel().name()); } if (options.getIndexingDirective() != null) { headers.put(HttpConstants.HttpHeaders.INDEXING_DIRECTIVE, options.getIndexingDirective().name()); } if (options.getPostTriggerInclude() != null && options.getPostTriggerInclude().size() > 0) { String postTriggerInclude = StringUtils.join(options.getPostTriggerInclude(), ","); headers.put(HttpConstants.HttpHeaders.POST_TRIGGER_INCLUDE, postTriggerInclude); } if (options.getPreTriggerInclude() != null && options.getPreTriggerInclude().size() > 0) { String preTriggerInclude = StringUtils.join(options.getPreTriggerInclude(), ","); headers.put(HttpConstants.HttpHeaders.PRE_TRIGGER_INCLUDE, preTriggerInclude); } if (options.getSessionToken() != null && !options.getSessionToken().isEmpty()) { headers.put(HttpConstants.HttpHeaders.SESSION_TOKEN, options.getSessionToken()); } if (options.getResourceTokenExpirySeconds() != null) { headers.put(HttpConstants.HttpHeaders.RESOURCE_TOKEN_EXPIRY, String.valueOf(options.getResourceTokenExpirySeconds())); } if (options.getOfferThroughput() != null && options.getOfferThroughput() >= 0) { headers.put(HttpConstants.HttpHeaders.OFFER_THROUGHPUT, options.getOfferThroughput().toString()); } else if (options.getOfferType() != null) { headers.put(HttpConstants.HttpHeaders.OFFER_TYPE, options.getOfferType()); } if (options.getPartitionKey() != null) { headers.put(HttpConstants.HttpHeaders.PARTITION_KEY, options.getPartitionKey().toString()); } if (options.isPopulateQuotaInfo()) { headers.put(HttpConstants.HttpHeaders.POPULATE_QUOTA_INFO, String.valueOf(true)); } if (options.getOfferEnableRUPerMinuteThroughput()) { headers.put(HttpConstants.HttpHeaders.OFFER_IS_RU_PER_MINUTE_THROUGHPUT_ENABLED, String.valueOf(true)); } if (options.getDisableRUPerMinuteUsage()) { headers.put(HttpConstants.HttpHeaders.DISABLE_RU_PER_MINUTE_USAGE, String.valueOf(true)); } if (options.isPopulatePartitionKeyRangeStatistics()) { headers.put(HttpConstants.HttpHeaders.POPULATE_PARTITION_KEY_RANGE_STATISTICS, String.valueOf(true)); } return headers; } private Map getMediaHeaders(MediaOptions options) { Map requestHeaders = new HashMap(); if (options == null || options.getContentType().isEmpty()) { requestHeaders.put(HttpConstants.HttpHeaders.CONTENT_TYPE, RuntimeConstants.MediaTypes.OCTET_STREAM); } if (options != null) { if (!options.getContentType().isEmpty()) { requestHeaders.put(HttpConstants.HttpHeaders.CONTENT_TYPE, options.getContentType()); } if (!options.getSlug().isEmpty()) { requestHeaders.put(HttpConstants.HttpHeaders.SLUG, options.getSlug()); } } return requestHeaders; } void addPartitionKeyInformation(AbstractDocumentServiceRequest request, Document document, RequestOptions options) { PartitionKeyInternal partitionKeyInternal; if (options != null && options.getPartitionKey() != null) { partitionKeyInternal = options.getPartitionKey().getInternalPartitionKey(); } else { // Resolve collection only when it's needed DocumentCollection collection = this.collectionCache.resolveCollection(request); PartitionKeyDefinition partitionKeyDefinition = collection.getPartitionKey(); if (partitionKeyDefinition == null || partitionKeyDefinition.getPaths().size() == 0) { // For backward compatibility, if collection doesn't have partition key defined, we assume all documents // have empty value for it and user doesn't need to specify it explicitly. partitionKeyInternal = PartitionKeyInternal.getEmpty(); } else if (document != null) { partitionKeyInternal = extractPartitionKeyValueFromDocument(document, partitionKeyDefinition); } else { throw new UnsupportedOperationException("PartitionKey value must be supplied for this operation."); } } request.getHeaders().put(HttpConstants.HttpHeaders.PARTITION_KEY, escapeNonAscii(partitionKeyInternal.toJson())); } private static String escapeNonAscii (String partitionKeyJson) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < partitionKeyJson.length(); i++) { int val = partitionKeyJson.charAt(i); if (val > 127) { sb.append("\\u" + String.format("%04X", val)); } else sb.append(partitionKeyJson.charAt(i)); } return sb.toString(); } private static PartitionKeyInternal extractPartitionKeyValueFromDocument( Document document, PartitionKeyDefinition partitionKeyDefinition) { if (partitionKeyDefinition != null) { String path = partitionKeyDefinition.getPaths().iterator().next(); Collection parts = PathParser.getPathParts(path); if (parts.size() >= 1) { Object value = document.getObjectByPath(parts); if (value == null || value.getClass() == JSONObject.class) { value = Undefined.Value(); } return PartitionKeyInternal.fromObjectArray(Arrays.asList(value), false); } } return null; } private StoreModel getStoreProxy(DocumentServiceRequest request) { ResourceType resourceType = request.getResourceType(); OperationType operationType = request.getOperationType(); if (resourceType == ResourceType.PartitionKeyRange || resourceType == ResourceType.DatabaseAccount || request.getIsMedia()) { return this.gatewayProxy; } if (Utils.isCollectionChild(request.getResourceType())) { DocumentCollection collection = this.collectionCache.resolveCollection(request); if (collection != null && Utils.isCollectionPartitioned(collection) && operationType == OperationType.Query && !ServiceJNIWrapper.isServiceJNIAvailable() && request.getPartitionKeyRangeIdentity() == null) { // Fallback to gateway to get query execution information for partitioned collection queries // when ServiceJNI is not available return this.gatewayProxy; } } if (resourceType == ResourceType.Offer || resourceType.isScript() && operationType != OperationType.ExecuteJavaScript) { return this.gatewayProxy; } if (operationType == OperationType.Create || operationType == OperationType.Upsert) { if (resourceType == ResourceType.Database || resourceType == ResourceType.User || resourceType == ResourceType.DocumentCollection || resourceType == ResourceType.Permission) { return this.gatewayProxy; } else { return this.storeModel; } } else if (operationType == OperationType.Delete) { if (resourceType == ResourceType.Database || resourceType == ResourceType.User || resourceType == ResourceType.DocumentCollection) { return this.gatewayProxy; } else { return this.storeModel; } } else if (operationType == OperationType.Replace) { if (resourceType == ResourceType.DocumentCollection) { return this.gatewayProxy; } else { return this.storeModel; } } else if (operationType == OperationType.Read) { if (resourceType == ResourceType.DocumentCollection) { return this.gatewayProxy; } else { return this.storeModel; } } else { return this.storeModel; } } void putMoreContentIntoDocumentServiceRequest(DocumentServiceRequest request, String httpMethod) { if (httpMethod != null) { request.setHttpMethod(httpMethod); } else { httpMethod = request.getHttpMethod(); } if (this.masterKey != null) { String xDate = Utils.getCurrentTimeGMT(); request.getHeaders().put(HttpConstants.HttpHeaders.X_DATE, xDate); } if (this.masterKey != null || this.resourceTokens != null) { String resourceName = request.getResourceFullName(); String authorization = this.getAuthorizationToken(resourceName, request.getPath(), request.getResourceType(), httpMethod, request.getHeaders(), this.masterKey, this.resourceTokens); try { authorization = URLEncoder.encode(authorization, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new IllegalStateException("Failed to encode authtoken.", e); } request.getHeaders().put(HttpConstants.HttpHeaders.AUTHORIZATION, authorization); } if ((HttpConstants.HttpMethods.POST.equals(httpMethod) || HttpConstants.HttpMethods.PUT.equals(httpMethod)) && !request.getHeaders().containsKey(HttpConstants.HttpHeaders.CONTENT_TYPE)) { request.getHeaders().put(HttpConstants.HttpHeaders.CONTENT_TYPE, RuntimeConstants.MediaTypes.JSON); } if (!request.getHeaders().containsKey(HttpConstants.HttpHeaders.ACCEPT)) { request.getHeaders().put(HttpConstants.HttpHeaders.ACCEPT, RuntimeConstants.MediaTypes.JSON); } } private String getAuthorizationToken(String resourceOrOwnerId, String path, ResourceType resourceType, String requestVerb, Map headers, String masterKey, Map resourceTokens) { if (masterKey != null) { return this.authorizationTokenProvider.generateKeyAuthorizationSignature(requestVerb, resourceOrOwnerId, resourceType, headers); } else if (resourceTokens != null) { return this.authorizationTokenProvider.getAuthorizationTokenUsingResourceTokens(path, resourceOrOwnerId); } return null; } StoreModel getStoreModel() { return this.storeModel; } ConcurrentHashMap getAddressCaches() { return this.globalAddressResolver.getAddressCaches(); } TransportClient getTransportClient() { return this.transportClient; } DatabaseAccountConfigurationProvider getDatabaseAccountConfigurationProvider() { return this.databaseAccountConfigurationProvider; } /** * Close this DocumentClient instance */ @Override public void close() { logger.info("Closing DocumentClient"); this.poolingHttpClientConnectionManager.shutdown(); this.globalEndpointManager.close(); this.executorService.shutdown(); try { this.executorService.awaitTermination(1, TimeUnit.MINUTES); } catch (InterruptedException e) { } } /** * An custom implementation of ThreadPoolExecutor to handle the exceptions that may happen in the worker threads. * In some scenarios such as parallel query, if a worker thread encounters exception the afterExecute should log the * trace for diagnostics purpose. Without this class, exceptions in worker threads terminate the thread and only * emerge when we check for the task result. */ static class DocumentDBThreadPoolExecutor extends ThreadPoolExecutor { private static final Logger LOGGER = LoggerFactory.getLogger(DocumentDBThreadPoolExecutor.class); DocumentDBThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override public void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t == null && r instanceof Future) { try { ((Future) r).get(); } catch (CancellationException ignored) { // The failure was in another task so this task was cancelled } catch (ExecutionException e) { t = e.getCause(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (t != null) { LOGGER.error("Runnable execution exception", t); } } } PartitionKeyRangeCache createPartitionKeyRangeCache() { return new PartitionKeyRangeCache(new DocumentQueryClientInternal(this)); } }