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

com.azure.cosmos.implementation.RxDocumentClientImpl Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.cosmos.implementation;

import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.SimpleTokenCache;
import com.azure.core.credential.TokenCredential;
import com.azure.core.credential.TokenRequestContext;
import com.azure.cosmos.BridgeInternal;
import com.azure.cosmos.ConnectionMode;
import com.azure.cosmos.ConsistencyLevel;
import com.azure.cosmos.CosmosAsyncClient;
import com.azure.cosmos.CosmosContainerProactiveInitConfig;
import com.azure.cosmos.CosmosDiagnostics;
import com.azure.cosmos.CosmosDiagnosticsContext;
import com.azure.cosmos.CosmosEndToEndOperationLatencyPolicyConfig;
import com.azure.cosmos.CosmosException;
import com.azure.cosmos.CosmosItemSerializer;
import com.azure.cosmos.CosmosOperationPolicy;
import com.azure.cosmos.DirectConnectionConfig;
import com.azure.cosmos.SessionRetryOptions;
import com.azure.cosmos.ThresholdBasedAvailabilityStrategy;
import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
import com.azure.cosmos.implementation.apachecommons.lang.tuple.ImmutablePair;
import com.azure.cosmos.implementation.batch.BatchResponseParser;
import com.azure.cosmos.implementation.batch.PartitionKeyRangeServerBatchRequest;
import com.azure.cosmos.implementation.batch.ServerBatchRequest;
import com.azure.cosmos.implementation.batch.SinglePartitionKeyServerBatchRequest;
import com.azure.cosmos.implementation.caches.RxClientCollectionCache;
import com.azure.cosmos.implementation.caches.RxCollectionCache;
import com.azure.cosmos.implementation.caches.RxPartitionKeyRangeCache;
import com.azure.cosmos.implementation.circuitBreaker.GlobalPartitionEndpointManagerForCircuitBreaker;
import com.azure.cosmos.implementation.circuitBreaker.PartitionKeyRangeWrapper;
import com.azure.cosmos.implementation.clienttelemetry.ClientTelemetry;
import com.azure.cosmos.implementation.cpu.CpuMemoryListener;
import com.azure.cosmos.implementation.cpu.CpuMemoryMonitor;
import com.azure.cosmos.implementation.directconnectivity.AddressSelector;
import com.azure.cosmos.implementation.directconnectivity.GatewayServiceConfigurationReader;
import com.azure.cosmos.implementation.directconnectivity.GlobalAddressResolver;
import com.azure.cosmos.implementation.directconnectivity.ServerStoreModel;
import com.azure.cosmos.implementation.directconnectivity.StoreClient;
import com.azure.cosmos.implementation.directconnectivity.StoreClientFactory;
import com.azure.cosmos.implementation.faultinjection.IFaultInjectorProvider;
import com.azure.cosmos.implementation.feedranges.FeedRangeEpkImpl;
import com.azure.cosmos.implementation.http.HttpClient;
import com.azure.cosmos.implementation.http.HttpClientConfig;
import com.azure.cosmos.implementation.http.HttpHeaders;
import com.azure.cosmos.implementation.http.SharedGatewayHttpClient;
import com.azure.cosmos.implementation.patch.PatchUtil;
import com.azure.cosmos.implementation.query.DocumentQueryExecutionContextFactory;
import com.azure.cosmos.implementation.query.IDocumentQueryClient;
import com.azure.cosmos.implementation.query.IDocumentQueryExecutionContext;
import com.azure.cosmos.implementation.query.Paginator;
import com.azure.cosmos.implementation.query.PartitionedQueryExecutionInfo;
import com.azure.cosmos.implementation.query.PipelinedQueryExecutionContextBase;
import com.azure.cosmos.implementation.query.QueryInfo;
import com.azure.cosmos.implementation.routing.CollectionRoutingMap;
import com.azure.cosmos.implementation.routing.PartitionKeyAndResourceTokenPair;
import com.azure.cosmos.implementation.routing.PartitionKeyInternal;
import com.azure.cosmos.implementation.routing.PartitionKeyInternalHelper;
import com.azure.cosmos.implementation.routing.PartitionKeyRangeIdentity;
import com.azure.cosmos.implementation.routing.Range;
import com.azure.cosmos.implementation.routing.RegionNameToRegionIdMap;
import com.azure.cosmos.implementation.spark.OperationContext;
import com.azure.cosmos.implementation.spark.OperationContextAndListenerTuple;
import com.azure.cosmos.implementation.spark.OperationListener;
import com.azure.cosmos.implementation.throughputControl.ThroughputControlStore;
import com.azure.cosmos.implementation.throughputControl.config.ThroughputControlGroupInternal;
import com.azure.cosmos.models.CosmosAuthorizationTokenResolver;
import com.azure.cosmos.models.CosmosBatchResponse;
import com.azure.cosmos.models.CosmosChangeFeedRequestOptions;
import com.azure.cosmos.models.CosmosClientTelemetryConfig;
import com.azure.cosmos.models.CosmosContainerIdentity;
import com.azure.cosmos.models.CosmosItemIdentity;
import com.azure.cosmos.models.CosmosItemRequestOptions;
import com.azure.cosmos.models.CosmosItemResponse;
import com.azure.cosmos.models.CosmosOperationDetails;
import com.azure.cosmos.models.CosmosPatchOperations;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.FeedRange;
import com.azure.cosmos.models.FeedResponse;
import com.azure.cosmos.models.ModelBridgeInternal;
import com.azure.cosmos.models.PartitionKey;
import com.azure.cosmos.models.PartitionKeyDefinition;
import com.azure.cosmos.models.PartitionKind;
import com.azure.cosmos.models.SqlParameter;
import com.azure.cosmos.models.SqlQuerySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;
import reactor.util.concurrent.Queues;
import reactor.util.function.Tuple2;
import reactor.util.retry.Retry;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.azure.cosmos.BridgeInternal.getAltLink;
import static com.azure.cosmos.BridgeInternal.toResourceResponse;
import static com.azure.cosmos.BridgeInternal.toStoredProcedureResponse;
import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkArgument;
import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull;

/**
 * While this class is public, it is not part of our published public APIs.
 * This is meant to be internally used only by our sdk.
 */
public class RxDocumentClientImpl implements AsyncDocumentClient, IAuthorizationTokenProvider, CpuMemoryListener,
    DiagnosticsClientContext {

    private final static List EMPTY_REGION_LIST = Collections.emptyList();

    private final static List EMPTY_ENDPOINT_LIST = Collections.emptyList();

    private final static
    ImplementationBridgeHelpers.CosmosDiagnosticsHelper.CosmosDiagnosticsAccessor diagnosticsAccessor =
        ImplementationBridgeHelpers.CosmosDiagnosticsHelper.getCosmosDiagnosticsAccessor();

    private final static
    ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor feedResponseAccessor =
        ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();

    private final static
    ImplementationBridgeHelpers.CosmosClientTelemetryConfigHelper.CosmosClientTelemetryConfigAccessor telemetryCfgAccessor =
        ImplementationBridgeHelpers.CosmosClientTelemetryConfigHelper.getCosmosClientTelemetryConfigAccessor();

    private final static
    ImplementationBridgeHelpers.CosmosDiagnosticsContextHelper.CosmosDiagnosticsContextAccessor ctxAccessor =
        ImplementationBridgeHelpers.CosmosDiagnosticsContextHelper.getCosmosDiagnosticsContextAccessor();

    private final static
    ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor qryOptAccessor =
        ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();

    private final static
    ImplementationBridgeHelpers.CosmosItemResponseHelper.CosmosItemResponseBuilderAccessor itemResponseAccessor =
        ImplementationBridgeHelpers.CosmosItemResponseHelper.getCosmosItemResponseBuilderAccessor();

    private static final ImplementationBridgeHelpers.CosmosChangeFeedRequestOptionsHelper.CosmosChangeFeedRequestOptionsAccessor changeFeedOptionsAccessor =
        ImplementationBridgeHelpers.CosmosChangeFeedRequestOptionsHelper.getCosmosChangeFeedRequestOptionsAccessor();

    private static final ImplementationBridgeHelpers.CosmosOperationDetailsHelper.CosmosOperationDetailsAccessor operationDetailsAccessor =
        ImplementationBridgeHelpers.CosmosOperationDetailsHelper.getCosmosOperationDetailsAccessor();

    private static final String tempMachineId = "uuid:" + UUID.randomUUID();
    private static final AtomicInteger activeClientsCnt = new AtomicInteger(0);
    private static final Map clientMap = new ConcurrentHashMap<>();
    private static final AtomicInteger clientIdGenerator = new AtomicInteger(0);
    private static final Range RANGE_INCLUDING_ALL_PARTITION_KEY_RANGES = new Range<>(
        PartitionKeyInternalHelper.MinimumInclusiveEffectivePartitionKey,
        PartitionKeyInternalHelper.MaximumExclusiveEffectivePartitionKey, true, false);

    private static final String DUMMY_SQL_QUERY = "this is dummy and only used in creating " +
        "ParallelDocumentQueryExecutioncontext, but not used";
    private final static ObjectMapper mapper = Utils.getSimpleObjectMapper();
    private final CosmosItemSerializer defaultCustomSerializer;
    private final static Logger logger = LoggerFactory.getLogger(RxDocumentClientImpl.class);
    private final String masterKeyOrResourceToken;
    private final URI serviceEndpoint;
    private final ConnectionPolicy connectionPolicy;
    private final ConsistencyLevel consistencyLevel;
    private final BaseAuthorizationTokenProvider authorizationTokenProvider;
    private final UserAgentContainer userAgentContainer;
    private final boolean hasAuthKeyResourceToken;
    private final Configs configs;
    private final boolean connectionSharingAcrossClientsEnabled;
    private AzureKeyCredential credential;
    private final TokenCredential tokenCredential;
    private String[] tokenCredentialScopes;
    private SimpleTokenCache tokenCredentialCache;
    private CosmosAuthorizationTokenResolver cosmosAuthorizationTokenResolver;
    AuthorizationTokenType authorizationTokenType;
    private ISessionContainer sessionContainer;
    private String firstResourceTokenFromPermissionFeed = StringUtils.EMPTY;
    private RxClientCollectionCache collectionCache;
    private RxGatewayStoreModel gatewayProxy;
    private RxStoreModel storeModel;
    private GlobalAddressResolver addressResolver;
    private RxPartitionKeyRangeCache partitionKeyRangeCache;
    private Map> resourceTokensMap;
    private final boolean contentResponseOnWriteEnabled;
    private final Map queryPlanCache;

    private final AtomicBoolean closed = new AtomicBoolean(false);
    private final int clientId;
    private ClientTelemetry clientTelemetry;
    private final ApiType apiType;
    private final CosmosEndToEndOperationLatencyPolicyConfig cosmosEndToEndOperationLatencyPolicyConfig;

    private final AtomicReference mostRecentlyCreatedDiagnostics = new AtomicReference<>(null);

    // RetryPolicy retries a request when it encounters session unavailable (see ClientRetryPolicy).
    // Once it exhausts all write regions it clears the session container, then it uses RxClientCollectionCache
    // to resolves the request's collection name. If it differs from the session container's resource id it
    // explains the session unavailable exception: somebody removed and recreated the collection. In this
    // case we retry once again (with empty session token) otherwise we return the error to the client
    // (see RenameCollectionAwareClientRetryPolicy)
    private IRetryPolicyFactory resetSessionTokenRetryPolicy;
    /**
     * 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 final QueryCompatibilityMode queryCompatibilityMode = QueryCompatibilityMode.Default;
    private final GlobalEndpointManager globalEndpointManager;
    private final GlobalPartitionEndpointManagerForCircuitBreaker globalPartitionEndpointManagerForCircuitBreaker;
    private final RetryPolicy retryPolicy;
    private HttpClient reactorHttpClient;
    private Function httpClientInterceptor;
    private volatile boolean useMultipleWriteLocations;

    // creator of TransportClient is responsible for disposing it.
    private StoreClientFactory storeClientFactory;

    private GatewayServiceConfigurationReader gatewayConfigurationReader;
    private final DiagnosticsClientConfig diagnosticsClientConfig;
    private final AtomicBoolean throughputControlEnabled;
    private ThroughputControlStore throughputControlStore;
    private final CosmosClientTelemetryConfig clientTelemetryConfig;
    private final String clientCorrelationId;
    private final SessionRetryOptions sessionRetryOptions;
    private final boolean sessionCapturingOverrideEnabled;
    private final boolean sessionCapturingDisabled;
    private final boolean isRegionScopedSessionCapturingEnabledOnClientOrSystemConfig;
    private List operationPolicies;
    private AtomicReference cachedCosmosAsyncClientSnapshot;

    public RxDocumentClientImpl(URI serviceEndpoint,
                                String masterKeyOrResourceToken,
                                List permissionFeed,
                                ConnectionPolicy connectionPolicy,
                                ConsistencyLevel consistencyLevel,
                                Configs configs,
                                CosmosAuthorizationTokenResolver cosmosAuthorizationTokenResolver,
                                AzureKeyCredential credential,
                                boolean sessionCapturingOverride,
                                boolean connectionSharingAcrossClientsEnabled,
                                boolean contentResponseOnWriteEnabled,
                                CosmosClientMetadataCachesSnapshot metadataCachesSnapshot,
                                ApiType apiType,
                                CosmosClientTelemetryConfig clientTelemetryConfig,
                                String clientCorrelationId,
                                CosmosEndToEndOperationLatencyPolicyConfig cosmosEndToEndOperationLatencyPolicyConfig,
                                SessionRetryOptions sessionRetryOptions,
                                CosmosContainerProactiveInitConfig containerProactiveInitConfig,
                                CosmosItemSerializer defaultCustomSerializer,
                                boolean isRegionScopedSessionCapturingEnabled) {
        this(
                serviceEndpoint,
                masterKeyOrResourceToken,
                permissionFeed,
                connectionPolicy,
                consistencyLevel,
                configs,
                credential,
                null,
                sessionCapturingOverride,
                connectionSharingAcrossClientsEnabled,
                contentResponseOnWriteEnabled,
                metadataCachesSnapshot,
                apiType,
                clientTelemetryConfig,
                clientCorrelationId,
                cosmosEndToEndOperationLatencyPolicyConfig,
                sessionRetryOptions,
                containerProactiveInitConfig,
                defaultCustomSerializer,
                isRegionScopedSessionCapturingEnabled);
        this.cosmosAuthorizationTokenResolver = cosmosAuthorizationTokenResolver;
    }

    public RxDocumentClientImpl(URI serviceEndpoint,
                                String masterKeyOrResourceToken,
                                List permissionFeed,
                                ConnectionPolicy connectionPolicy,
                                ConsistencyLevel consistencyLevel,
                                Configs configs,
                                CosmosAuthorizationTokenResolver cosmosAuthorizationTokenResolver,
                                AzureKeyCredential credential,
                                TokenCredential tokenCredential,
                                boolean sessionCapturingOverride,
                                boolean connectionSharingAcrossClientsEnabled,
                                boolean contentResponseOnWriteEnabled,
                                CosmosClientMetadataCachesSnapshot metadataCachesSnapshot,
                                ApiType apiType,
                                CosmosClientTelemetryConfig clientTelemetryConfig,
                                String clientCorrelationId,
                                CosmosEndToEndOperationLatencyPolicyConfig cosmosEndToEndOperationLatencyPolicyConfig,
                                SessionRetryOptions sessionRetryOptions,
                                CosmosContainerProactiveInitConfig containerProactiveInitConfig,
                                CosmosItemSerializer defaultCustomSerializer,
                                boolean isRegionScopedSessionCapturingEnabled,
                                List operationPolicies) {
        this(
                serviceEndpoint,
                masterKeyOrResourceToken,
                permissionFeed,
                connectionPolicy,
                consistencyLevel,
                configs,
                credential,
                tokenCredential,
                sessionCapturingOverride,
                connectionSharingAcrossClientsEnabled,
                contentResponseOnWriteEnabled,
                metadataCachesSnapshot,
                apiType,
                clientTelemetryConfig,
                clientCorrelationId,
                cosmosEndToEndOperationLatencyPolicyConfig,
                sessionRetryOptions,
                containerProactiveInitConfig,
                defaultCustomSerializer,
                isRegionScopedSessionCapturingEnabled);
        this.cosmosAuthorizationTokenResolver = cosmosAuthorizationTokenResolver;
        this.operationPolicies = operationPolicies;
    }

    private RxDocumentClientImpl(URI serviceEndpoint,
                                String masterKeyOrResourceToken,
                                List permissionFeed,
                                ConnectionPolicy connectionPolicy,
                                ConsistencyLevel consistencyLevel,
                                Configs configs,
                                AzureKeyCredential credential,
                                TokenCredential tokenCredential,
                                boolean sessionCapturingOverrideEnabled,
                                boolean connectionSharingAcrossClientsEnabled,
                                boolean contentResponseOnWriteEnabled,
                                CosmosClientMetadataCachesSnapshot metadataCachesSnapshot,
                                ApiType apiType,
                                CosmosClientTelemetryConfig clientTelemetryConfig,
                                String clientCorrelationId,
                                CosmosEndToEndOperationLatencyPolicyConfig cosmosEndToEndOperationLatencyPolicyConfig,
                                SessionRetryOptions sessionRetryOptions,
                                CosmosContainerProactiveInitConfig containerProactiveInitConfig,
                                CosmosItemSerializer defaultCustomSerializer,
                                boolean isRegionScopedSessionCapturingEnabled) {
        this(
                serviceEndpoint,
                masterKeyOrResourceToken,
                connectionPolicy,
                consistencyLevel,
                configs,
                credential,
                tokenCredential,
                sessionCapturingOverrideEnabled,
                connectionSharingAcrossClientsEnabled,
                contentResponseOnWriteEnabled,
                metadataCachesSnapshot,
                apiType,
                clientTelemetryConfig,
                clientCorrelationId,
                cosmosEndToEndOperationLatencyPolicyConfig,
                sessionRetryOptions,
                containerProactiveInitConfig,
                defaultCustomSerializer,
                isRegionScopedSessionCapturingEnabled);

        if (permissionFeed != null && permissionFeed.size() > 0) {
            this.resourceTokensMap = new HashMap<>();
            for (Permission permission : permissionFeed) {
                String[] segments = StringUtils.split(permission.getResourceLink(),
                        Constants.Properties.PATH_SEPARATOR.charAt(0));

                if (segments.length == 0) {
                    throw new IllegalArgumentException("resourceLink");
                }

                List partitionKeyAndResourceTokenPairs = null;
                PathInfo pathInfo = new PathInfo(false, StringUtils.EMPTY, StringUtils.EMPTY, false);
                if (!PathsHelper.tryParsePathSegments(permission.getResourceLink(), pathInfo, null)) {
                    throw new IllegalArgumentException(permission.getResourceLink());
                }

                partitionKeyAndResourceTokenPairs = resourceTokensMap.get(pathInfo.resourceIdOrFullName);
                if (partitionKeyAndResourceTokenPairs == null) {
                    partitionKeyAndResourceTokenPairs = new ArrayList<>();
                    this.resourceTokensMap.put(pathInfo.resourceIdOrFullName, partitionKeyAndResourceTokenPairs);
                }

                PartitionKey partitionKey = permission.getResourcePartitionKey();
                partitionKeyAndResourceTokenPairs.add(new PartitionKeyAndResourceTokenPair(
                        partitionKey != null ? BridgeInternal.getPartitionKeyInternal(partitionKey) : PartitionKeyInternal.Empty,
                        permission.getToken()));
                logger.debug("Initializing resource token map  , with map key [{}] , partition key [{}] and resource token [{}]",
                        pathInfo.resourceIdOrFullName, partitionKey != null ? partitionKey.toString() : null, permission.getToken());

            }

            if(this.resourceTokensMap.isEmpty()) {
                throw new IllegalArgumentException("permissionFeed");
            }

            String firstToken = permissionFeed.get(0).getToken();
            if(ResourceTokenAuthorizationHelper.isResourceToken(firstToken)) {
                this.firstResourceTokenFromPermissionFeed = firstToken;
            }
        }
    }

    RxDocumentClientImpl(URI serviceEndpoint,
                         String masterKeyOrResourceToken,
                         ConnectionPolicy connectionPolicy,
                         ConsistencyLevel consistencyLevel,
                         Configs configs,
                         AzureKeyCredential credential,
                         TokenCredential tokenCredential,
                         boolean sessionCapturingOverrideEnabled,
                         boolean connectionSharingAcrossClientsEnabled,
                         boolean contentResponseOnWriteEnabled,
                         CosmosClientMetadataCachesSnapshot metadataCachesSnapshot,
                         ApiType apiType,
                         CosmosClientTelemetryConfig clientTelemetryConfig,
                         String clientCorrelationId,
                         CosmosEndToEndOperationLatencyPolicyConfig cosmosEndToEndOperationLatencyPolicyConfig,
                         SessionRetryOptions sessionRetryOptions,
                         CosmosContainerProactiveInitConfig containerProactiveInitConfig,
                         CosmosItemSerializer defaultCustomSerializer,
                         boolean isRegionScopedSessionCapturingEnabled) {

        assert(clientTelemetryConfig != null);
        Boolean clientTelemetryEnabled = ImplementationBridgeHelpers
            .CosmosClientTelemetryConfigHelper
            .getCosmosClientTelemetryConfigAccessor()
            .isSendClientTelemetryToServiceEnabled(clientTelemetryConfig);
        assert(clientTelemetryEnabled != null);
        activeClientsCnt.incrementAndGet();
        this.clientId = clientIdGenerator.incrementAndGet();
        this.clientCorrelationId = Strings.isNullOrWhiteSpace(clientCorrelationId) ?
            String.format("%05d",this.clientId): clientCorrelationId;
        clientMap.put(serviceEndpoint.toString(), clientMap.getOrDefault(serviceEndpoint.toString(), 0) + 1);
        this.diagnosticsClientConfig = new DiagnosticsClientConfig();
        this.diagnosticsClientConfig.withClientId(this.clientId);
        this.diagnosticsClientConfig.withActiveClientCounter(activeClientsCnt);
        this.diagnosticsClientConfig.withClientMap(clientMap);

        this.diagnosticsClientConfig.withConnectionSharingAcrossClientsEnabled(connectionSharingAcrossClientsEnabled);
        this.diagnosticsClientConfig.withConsistency(consistencyLevel);
        this.throughputControlEnabled = new AtomicBoolean(false);
        this.cosmosEndToEndOperationLatencyPolicyConfig = cosmosEndToEndOperationLatencyPolicyConfig;
        this.diagnosticsClientConfig.withEndToEndOperationLatencyPolicy(cosmosEndToEndOperationLatencyPolicyConfig);
        this.sessionRetryOptions = sessionRetryOptions;
        this.defaultCustomSerializer = defaultCustomSerializer;

        logger.info(
            "Initializing DocumentClient [{}] with"
                + " serviceEndpoint [{}], connectionPolicy [{}], consistencyLevel [{}]",
            this.clientId, serviceEndpoint, connectionPolicy, consistencyLevel, configs.getProtocol());

        try {
            this.connectionSharingAcrossClientsEnabled = connectionSharingAcrossClientsEnabled;
            this.configs = configs;
            this.masterKeyOrResourceToken = masterKeyOrResourceToken;
            this.serviceEndpoint = serviceEndpoint;
            this.credential = credential;
            this.tokenCredential = tokenCredential;
            this.contentResponseOnWriteEnabled = contentResponseOnWriteEnabled;
            this.authorizationTokenType = AuthorizationTokenType.Invalid;

            if (this.credential != null) {
                hasAuthKeyResourceToken = false;
                this.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey;
                this.authorizationTokenProvider = new BaseAuthorizationTokenProvider(this.credential);
            } else if (masterKeyOrResourceToken != null && ResourceTokenAuthorizationHelper.isResourceToken(masterKeyOrResourceToken)) {
                this.authorizationTokenProvider = null;
                hasAuthKeyResourceToken = true;
                this.authorizationTokenType = AuthorizationTokenType.ResourceToken;
            } else if(masterKeyOrResourceToken != null && !ResourceTokenAuthorizationHelper.isResourceToken(masterKeyOrResourceToken)) {
                this.credential = new AzureKeyCredential(this.masterKeyOrResourceToken);
                hasAuthKeyResourceToken = false;
                this.authorizationTokenType = AuthorizationTokenType.PrimaryMasterKey;
                this.authorizationTokenProvider = new BaseAuthorizationTokenProvider(this.credential);
            } else {
                hasAuthKeyResourceToken = false;
                this.authorizationTokenProvider = null;
                if (tokenCredential != null) {
                    this.tokenCredentialScopes = new String[] {
//                    AadTokenAuthorizationHelper.AAD_AUTH_TOKEN_COSMOS_WINDOWS_SCOPE,
                        serviceEndpoint.getScheme() + "://" + serviceEndpoint.getHost() + "/.default"
                    };
                    this.tokenCredentialCache = new SimpleTokenCache(() -> this.tokenCredential
                        .getToken(new TokenRequestContext().addScopes(this.tokenCredentialScopes)));
                    this.authorizationTokenType = AuthorizationTokenType.AadToken;
                }
            }

            if (connectionPolicy != null) {
                this.connectionPolicy = connectionPolicy;
            } else {
                this.connectionPolicy = new ConnectionPolicy(DirectConnectionConfig.getDefaultConfig());
            }

            this.diagnosticsClientConfig.withConnectionMode(this.getConnectionPolicy().getConnectionMode());
            this.diagnosticsClientConfig.withConnectionPolicy(this.connectionPolicy);
            this.diagnosticsClientConfig.withMultipleWriteRegionsEnabled(this.connectionPolicy.isMultipleWriteRegionsEnabled());
            this.diagnosticsClientConfig.withEndpointDiscoveryEnabled(this.connectionPolicy.isEndpointDiscoveryEnabled());
            this.diagnosticsClientConfig.withPreferredRegions(this.connectionPolicy.getPreferredRegions());
            this.diagnosticsClientConfig.withMachineId(tempMachineId);
            this.diagnosticsClientConfig.withProactiveContainerInitConfig(containerProactiveInitConfig);
            this.diagnosticsClientConfig.withSessionRetryOptions(sessionRetryOptions);

            this.sessionCapturingOverrideEnabled = sessionCapturingOverrideEnabled;
            boolean disableSessionCapturing = (ConsistencyLevel.SESSION != consistencyLevel && !sessionCapturingOverrideEnabled);
            this.sessionCapturingDisabled = disableSessionCapturing;

            this.consistencyLevel = consistencyLevel;

            this.userAgentContainer = new UserAgentContainer();

            String userAgentSuffix = this.connectionPolicy.getUserAgentSuffix();
            if (userAgentSuffix != null && userAgentSuffix.length() > 0) {
                userAgentContainer.setSuffix(userAgentSuffix);
            }

            this.httpClientInterceptor = null;
            this.reactorHttpClient = httpClient();

            this.globalEndpointManager = new GlobalEndpointManager(asDatabaseAccountManagerInternal(), this.connectionPolicy, /**/configs);
            this.isRegionScopedSessionCapturingEnabledOnClientOrSystemConfig = isRegionScopedSessionCapturingEnabled;

            this.sessionContainer = new SessionContainer(this.serviceEndpoint.getHost(), disableSessionCapturing);

            this.globalPartitionEndpointManagerForCircuitBreaker = new GlobalPartitionEndpointManagerForCircuitBreaker(this.globalEndpointManager);

            this.globalPartitionEndpointManagerForCircuitBreaker.init();
            this.cachedCosmosAsyncClientSnapshot = new AtomicReference<>();

            this.diagnosticsClientConfig.withPartitionLevelCircuitBreakerConfig(this.globalPartitionEndpointManagerForCircuitBreaker.getCircuitBreakerConfig());

            this.retryPolicy = new RetryPolicy(
                this,
                this.globalEndpointManager,
                this.connectionPolicy,
                this.globalPartitionEndpointManagerForCircuitBreaker);
            this.resetSessionTokenRetryPolicy = retryPolicy;
            CpuMemoryMonitor.register(this);
            this.queryPlanCache = new ConcurrentHashMap<>();
            this.apiType = apiType;
            this.clientTelemetryConfig = clientTelemetryConfig;
        } catch (RuntimeException e) {
            logger.error("unexpected failure in initializing client.", e);
            close();
            throw e;
        }
    }

    @Override
    public DiagnosticsClientConfig getConfig() {
        return diagnosticsClientConfig;
    }

    @Override
    public CosmosDiagnostics createDiagnostics() {
       CosmosDiagnostics diagnostics =
           diagnosticsAccessor.create(this, telemetryCfgAccessor.getSamplingRate(this.clientTelemetryConfig));

       this.mostRecentlyCreatedDiagnostics.set(diagnostics);

       return diagnostics;
    }
    private DatabaseAccount initializeGatewayConfigurationReader() {
        this.gatewayConfigurationReader = new GatewayServiceConfigurationReader(this.globalEndpointManager);
        DatabaseAccount databaseAccount = this.globalEndpointManager.getLatestDatabaseAccount();
        //Database account should not be null here,
        // this.globalEndpointManager.init() must have been already called
        // hence asserting it
        if (databaseAccount == null) {
            Throwable databaseRefreshErrorSnapshot = this.globalEndpointManager.getLatestDatabaseRefreshError();
            if (databaseRefreshErrorSnapshot != null) {
                logger.error("Client initialization failed. Check if the endpoint is reachable and if your auth token "
                        + "is valid. More info: https://aka.ms/cosmosdb-tsg-service-unavailable-java. More details: "+ databaseRefreshErrorSnapshot.getMessage(),
                    databaseRefreshErrorSnapshot
                );

                throw new RuntimeException("Client initialization failed. Check if the endpoint is reachable and if your auth token "
                    + "is valid. More info: https://aka.ms/cosmosdb-tsg-service-unavailable-java. More details: "+ databaseRefreshErrorSnapshot.getMessage(),
                    databaseRefreshErrorSnapshot);
            } else {
                logger.error("Client initialization failed."
                    + " Check if the endpoint is reachable and if your auth token is valid. More info: https://aka.ms/cosmosdb-tsg-service-unavailable-java");

                throw new RuntimeException("Client initialization failed. Check if the endpoint is reachable and if your auth token "
                    + "is valid. More info: https://aka.ms/cosmosdb-tsg-service-unavailable-java.");
            }
        }

        this.useMultipleWriteLocations = this.connectionPolicy.isMultipleWriteRegionsEnabled() && BridgeInternal.isEnableMultipleWriteLocations(databaseAccount);
        return databaseAccount;
        // TODO: add support for openAsync
        // https://msdata.visualstudio.com/CosmosDB/_workitems/edit/332589
    }

    private void resetSessionContainerIfNeeded(DatabaseAccount databaseAccount) {
        boolean isRegionScopingOfSessionTokensPossible = this.isRegionScopingOfSessionTokensPossible(databaseAccount, this.useMultipleWriteLocations, this.isRegionScopedSessionCapturingEnabledOnClientOrSystemConfig);

        if (isRegionScopingOfSessionTokensPossible) {
            this.sessionContainer = new RegionScopedSessionContainer(this.serviceEndpoint.getHost(), this.sessionCapturingDisabled, this.globalEndpointManager);
            this.diagnosticsClientConfig.withRegionScopedSessionContainerOptions((RegionScopedSessionContainer) this.sessionContainer);
        }
    }

    private boolean isRegionScopingOfSessionTokensPossible(DatabaseAccount databaseAccount, boolean useMultipleWriteLocations, boolean isRegionScopedSessionCapturingEnabled) {

        if (!isRegionScopedSessionCapturingEnabled) {
            return false;
        }

        if (!useMultipleWriteLocations) {
            return false;
        }

        Iterable readableLocationsIterable = databaseAccount.getReadableLocations();
        Iterator readableLocationsIterator = readableLocationsIterable.iterator();

        while (readableLocationsIterator.hasNext()) {
            DatabaseAccountLocation readableLocation = readableLocationsIterator.next();

            String normalizedReadableRegion = readableLocation.getName().toLowerCase(Locale.ROOT).trim().replace(" ", "");

            if (RegionNameToRegionIdMap.getRegionId(normalizedReadableRegion) == -1) {
                return false;
            }
        }

        return true;
    }

    private void updateGatewayProxy() {
        (this.gatewayProxy).setGatewayServiceConfigurationReader(this.gatewayConfigurationReader);
        (this.gatewayProxy).setCollectionCache(this.collectionCache);
        (this.gatewayProxy).setPartitionKeyRangeCache(this.partitionKeyRangeCache);
        (this.gatewayProxy).setUseMultipleWriteLocations(this.useMultipleWriteLocations);
        (this.gatewayProxy).setSessionContainer(this.sessionContainer);
    }

    public void init(CosmosClientMetadataCachesSnapshot metadataCachesSnapshot, Function httpClientInterceptor) {
        try {
            // TODO: add support for openAsync
            // https://msdata.visualstudio.com/CosmosDB/_workitems/edit/332589

            this.httpClientInterceptor = httpClientInterceptor;
            if (httpClientInterceptor != null) {
                this.reactorHttpClient = httpClientInterceptor.apply(httpClient());
            }

            this.gatewayProxy = createRxGatewayProxy(this.sessionContainer,
                this.consistencyLevel,
                this.queryCompatibilityMode,
                this.userAgentContainer,
                this.globalEndpointManager,
                this.reactorHttpClient,
                this.apiType);

            this.globalEndpointManager.init();

            DatabaseAccount databaseAccountSnapshot = this.initializeGatewayConfigurationReader();
            this.resetSessionContainerIfNeeded(databaseAccountSnapshot);

            if (metadataCachesSnapshot != null) {
                this.collectionCache = new RxClientCollectionCache(this,
                    this.sessionContainer,
                    this.gatewayProxy,
                    this,
                    this.retryPolicy,
                    metadataCachesSnapshot.getCollectionInfoByNameCache(),
                    metadataCachesSnapshot.getCollectionInfoByIdCache()
                );
            } else {
                this.collectionCache = new RxClientCollectionCache(this,
                    this.sessionContainer,
                    this.gatewayProxy,
                    this,
                    this.retryPolicy);
            }
            this.resetSessionTokenRetryPolicy = new ResetSessionTokenRetryPolicyFactory(this.sessionContainer, this.collectionCache, this.retryPolicy);

            this.partitionKeyRangeCache = new RxPartitionKeyRangeCache(RxDocumentClientImpl.this,
                collectionCache);

            updateGatewayProxy();
            clientTelemetry = new ClientTelemetry(
                    this,
                    null,
                    randomUuid().toString(),
                    ManagementFactory.getRuntimeMXBean().getName(),
                    connectionPolicy.getConnectionMode(),
                    globalEndpointManager.getLatestDatabaseAccount().getId(),
                    null,
                    null,
                    this.configs,
                    this.clientTelemetryConfig,
                    this,
                    this.connectionPolicy.getPreferredRegions());
            clientTelemetry.init().thenEmpty((publisher) -> {
                logger.warn(
                    "Initialized DocumentClient [{}] with machineId[{}]"
                        + " serviceEndpoint [{}], connectionPolicy [{}], consistencyLevel [{}]",
                    clientId,
                    ClientTelemetry.getMachineId(diagnosticsClientConfig),
                    serviceEndpoint,
                    connectionPolicy,
                    consistencyLevel);
            }).subscribe();
            if (this.connectionPolicy.getConnectionMode() == ConnectionMode.GATEWAY) {
                this.storeModel = this.gatewayProxy;
            } else {
                this.initializeDirectConnectivity();
            }
            this.retryPolicy.setRxCollectionCache(this.collectionCache);
            ConsistencyLevel effectiveConsistencyLevel = consistencyLevel != null
                ? consistencyLevel
                : this.getDefaultConsistencyLevelOfAccount();
            boolean updatedDisableSessionCapturing =
                (ConsistencyLevel.SESSION != effectiveConsistencyLevel && !sessionCapturingOverrideEnabled);
            this.sessionContainer.setDisableSessionCapturing(updatedDisableSessionCapturing);
        } catch (Exception e) {
            logger.error("unexpected failure in initializing client.", e);
            close();
            throw e;
        }
    }

    public void serialize(CosmosClientMetadataCachesSnapshot state) {
        RxCollectionCache.serialize(state, this.collectionCache);
    }

    private void initializeDirectConnectivity() {
        this.addressResolver = new GlobalAddressResolver(this,
            this.reactorHttpClient,
            this.globalEndpointManager,
            this.configs.getProtocol(),
            this,
            this.collectionCache,
            this.partitionKeyRangeCache,
            userAgentContainer,
            // TODO: GATEWAY Configuration Reader
            //     this.gatewayConfigurationReader,
            null,
            this.connectionPolicy,
            this.apiType);

        this.storeClientFactory = new StoreClientFactory(
            this.addressResolver,
            this.diagnosticsClientConfig,
            this.configs,
            this.connectionPolicy,
            // this.maxConcurrentConnectionOpenRequests,
            this.userAgentContainer,
            this.connectionSharingAcrossClientsEnabled,
            this.clientTelemetry,
            this.globalEndpointManager);

        this.globalPartitionEndpointManagerForCircuitBreaker.setGlobalAddressResolver(this.addressResolver);
        this.createStoreModel(true);
    }

    DatabaseAccountManagerInternal asDatabaseAccountManagerInternal() {
        return new DatabaseAccountManagerInternal() {

            @Override
            public URI getServiceEndpoint() {
                return RxDocumentClientImpl.this.getServiceEndpoint();
            }

            @Override
            public Flux getDatabaseAccountFromEndpoint(URI endpoint) {
                logger.info("Getting database account endpoint from {}", endpoint);
                return RxDocumentClientImpl.this.getDatabaseAccountFromEndpoint(endpoint);
            }

            @Override
            public ConnectionPolicy getConnectionPolicy() {
                return RxDocumentClientImpl.this.getConnectionPolicy();
            }
        };
    }

    RxGatewayStoreModel createRxGatewayProxy(ISessionContainer sessionContainer,
                                             ConsistencyLevel consistencyLevel,
                                             QueryCompatibilityMode queryCompatibilityMode,
                                             UserAgentContainer userAgentContainer,
                                             GlobalEndpointManager globalEndpointManager,
                                             HttpClient httpClient,
                                             ApiType apiType) {
        return new RxGatewayStoreModel(
                this,
                sessionContainer,
                consistencyLevel,
                queryCompatibilityMode,
                userAgentContainer,
                globalEndpointManager,
                httpClient,
                apiType);
    }

    private HttpClient httpClient() {
        HttpClientConfig httpClientConfig = new HttpClientConfig(this.configs)
            .withMaxIdleConnectionTimeout(this.connectionPolicy.getIdleHttpConnectionTimeout())
            .withPoolSize(this.connectionPolicy.getMaxConnectionPoolSize())
            .withProxy(this.connectionPolicy.getProxy())
            .withNetworkRequestTimeout(this.connectionPolicy.getHttpNetworkRequestTimeout());

        if (connectionSharingAcrossClientsEnabled) {
            return SharedGatewayHttpClient.getOrCreateInstance(httpClientConfig, diagnosticsClientConfig);
        } else {
            diagnosticsClientConfig.withGatewayHttpClientConfig(httpClientConfig.toDiagnosticsString());
            return HttpClient.createFixed(httpClientConfig);
        }
    }

    private void createStoreModel(boolean subscribeRntbdStatus) {
        // EnableReadRequestsFallback, if not explicitly set on the connection policy,
        // is false if the account's consistency is bounded staleness,
        // and true otherwise.

        StoreClient storeClient = this.storeClientFactory.createStoreClient(this,
                this.addressResolver,
                this.sessionContainer,
                this.gatewayConfigurationReader,
                this,
                this.useMultipleWriteLocations,
                this.sessionRetryOptions);

        this.storeModel = new ServerStoreModel(storeClient);
    }


    @Override
    public URI getServiceEndpoint() {
        return this.serviceEndpoint;
    }

    @Override
    public ConnectionPolicy getConnectionPolicy() {
        return this.connectionPolicy;
    }

    @Override
    public boolean isContentResponseOnWriteEnabled() {
        return contentResponseOnWriteEnabled;
    }

    @Override
    public ConsistencyLevel getConsistencyLevel() {
        return consistencyLevel;
    }

    @Override
    public ClientTelemetry getClientTelemetry() {
        return this.clientTelemetry;
    }

    @Override
    public String getClientCorrelationId() {
        return this.clientCorrelationId;
    }

    @Override
    public String getMachineId() {
        if (this.diagnosticsClientConfig == null) {
            return null;
        }

        return ClientTelemetry.getMachineId(diagnosticsClientConfig);
    }

    @Override
    public String getUserAgent() {
        return this.userAgentContainer.getUserAgent();
    }

    @Override
    public CosmosDiagnostics getMostRecentlyCreatedDiagnostics() {
        return mostRecentlyCreatedDiagnostics.get();
    }

    @Override
    public Mono> createDatabase(Database database, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> createDatabaseInternal(database, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> createDatabaseInternal(Database database, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {

            if (database == null) {
                throw new IllegalArgumentException("Database");
            }

            logger.debug("Creating a Database. id: [{}]", database.getId());
            validateResource(database);

            Map requestHeaders = this.getRequestHeaders(options, ResourceType.Database, OperationType.Create);
            Instant serializationStartTimeUTC = Instant.now();
            ByteBuffer byteBuffer = database.serializeJsonToByteBuffer(CosmosItemSerializer.DEFAULT_SERIALIZER, null, false);
            Instant serializationEndTimeUTC = Instant.now();
            SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics = new SerializationDiagnosticsContext.SerializationDiagnostics(
                serializationStartTimeUTC,
                serializationEndTimeUTC,
                SerializationDiagnosticsContext.SerializationType.DATABASE_SERIALIZATION);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Create, ResourceType.Database, Paths.DATABASES_ROOT, byteBuffer, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            SerializationDiagnosticsContext serializationDiagnosticsContext = BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);
            if (serializationDiagnosticsContext != null) {
                serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
            }

            return this.create(request, retryPolicyInstance, getOperationContextAndListenerTuple(options))
                       .map(response -> toResourceResponse(response, Database.class));
        } catch (Exception e) {
            logger.debug("Failure in creating a database. due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> deleteDatabase(String databaseLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteDatabaseInternal(databaseLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> deleteDatabaseInternal(String databaseLink, RequestOptions options,
                                                                    DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (StringUtils.isEmpty(databaseLink)) {
                throw new IllegalArgumentException("databaseLink");
            }

            logger.debug("Deleting a Database. databaseLink: [{}]", databaseLink);
            String path = Utils.joinPath(databaseLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.Database, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.Database, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, Database.class));
        } catch (Exception e) {
            logger.debug("Failure in deleting a database. due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> readDatabase(String databaseLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readDatabaseInternal(databaseLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readDatabaseInternal(String databaseLink, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (StringUtils.isEmpty(databaseLink)) {
                throw new IllegalArgumentException("databaseLink");
            }

            logger.debug("Reading a Database. databaseLink: [{}]", databaseLink);
            String path = Utils.joinPath(databaseLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.Database, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.Database, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }
            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, Database.class));
        } catch (Exception e) {
            logger.debug("Failure in reading a database. due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readDatabases(QueryFeedOperationState state) {
        return nonDocumentReadFeed(state, ResourceType.Database, Database.class, Paths.DATABASES_ROOT);
    }

    private String parentResourceLinkToQueryLink(String parentResourceLink, ResourceType resourceTypeEnum) {
        switch (resourceTypeEnum) {
            case Database:
                return Paths.DATABASES_ROOT;

            case DocumentCollection:
                return Utils.joinPath(parentResourceLink, Paths.COLLECTIONS_PATH_SEGMENT);

            case Document:
                return Utils.joinPath(parentResourceLink, Paths.DOCUMENTS_PATH_SEGMENT);

            case Offer:
                return Paths.OFFERS_ROOT;

            case User:
                return Utils.joinPath(parentResourceLink, Paths.USERS_PATH_SEGMENT);

            case ClientEncryptionKey:
                return Utils.joinPath(parentResourceLink, Paths.CLIENT_ENCRYPTION_KEY_PATH_SEGMENT);

            case Permission:
                return Utils.joinPath(parentResourceLink, Paths.PERMISSIONS_PATH_SEGMENT);

            case Attachment:
                return Utils.joinPath(parentResourceLink, Paths.ATTACHMENTS_PATH_SEGMENT);

            case StoredProcedure:
                return Utils.joinPath(parentResourceLink, Paths.STORED_PROCEDURES_PATH_SEGMENT);

            case Trigger:
                return Utils.joinPath(parentResourceLink, Paths.TRIGGERS_PATH_SEGMENT);

            case UserDefinedFunction:
                return Utils.joinPath(parentResourceLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT);

            case Conflict:
                return Utils.joinPath(parentResourceLink, Paths.CONFLICTS_PATH_SEGMENT);

            default:
                throw new IllegalArgumentException("resource type not supported");
        }
    }

    private OperationContextAndListenerTuple getOperationContextAndListenerTuple(CosmosQueryRequestOptions options) {
        if (options == null) {
            return null;
        }
        return qryOptAccessor.getImpl(options).getOperationContextAndListenerTuple();
    }

    private OperationContextAndListenerTuple getOperationContextAndListenerTuple(RequestOptions options) {
        if (options == null) {
            return null;
        }
        return options.getOperationContextAndListenerTuple();
    }

    private  Flux> createQuery(
        String parentResourceLink,
        SqlQuerySpec sqlQuery,
        QueryFeedOperationState state,
        Class klass,
        ResourceType resourceTypeEnum) {

        return createQuery(parentResourceLink, sqlQuery, state, klass, resourceTypeEnum, this);
    }

    private  Flux> createQuery(
        String parentResourceLink,
        SqlQuerySpec sqlQuery,
        QueryFeedOperationState state,
        Class klass,
        ResourceType resourceTypeEnum,
        DiagnosticsClientContext innerDiagnosticsFactory) {

        String resourceLink = parentResourceLinkToQueryLink(parentResourceLink, resourceTypeEnum);

        CosmosQueryRequestOptions nonNullQueryOptions = state.getQueryOptions();

        UUID correlationActivityIdOfRequestOptions = qryOptAccessor
            .getImpl(nonNullQueryOptions)
            .getCorrelationActivityId();
        UUID correlationActivityId = correlationActivityIdOfRequestOptions != null ?
            correlationActivityIdOfRequestOptions : randomUuid();

        final AtomicBoolean isQueryCancelledOnTimeout = new AtomicBoolean(false);

        IDocumentQueryClient queryClient = documentQueryClientImpl(RxDocumentClientImpl.this, getOperationContextAndListenerTuple(nonNullQueryOptions));

        // Trying to put this logic as low as the query pipeline
        // Since for parallelQuery, each partition will have its own request, so at this point, there will be no request associate with this retry policy.
        // For default document context, it already wired up InvalidPartitionExceptionRetry, but there is no harm to wire it again here
        InvalidPartitionExceptionRetryPolicy invalidPartitionExceptionRetryPolicy = new InvalidPartitionExceptionRetryPolicy(
            this.collectionCache,
            null,
            resourceLink,
            ModelBridgeInternal.getPropertiesFromQueryRequestOptions(nonNullQueryOptions));


        final ScopedDiagnosticsFactory diagnosticsFactory = new ScopedDiagnosticsFactory(innerDiagnosticsFactory, false);
        state.registerDiagnosticsFactory(
            diagnosticsFactory::reset,
            diagnosticsFactory::merge);

        return
            ObservableHelper.fluxInlineIfPossibleAsObs(
                                () -> createQueryInternal(
                                    diagnosticsFactory, resourceLink, sqlQuery, state.getQueryOptions(), klass, resourceTypeEnum, queryClient, correlationActivityId, isQueryCancelledOnTimeout),
                                invalidPartitionExceptionRetryPolicy
                            ).flatMap(result -> {
                                diagnosticsFactory.merge(state.getDiagnosticsContextSnapshot());
                                return Mono.just(result);
                            })
                            .onErrorMap(throwable -> {
                                diagnosticsFactory.merge(state.getDiagnosticsContextSnapshot());
                                return throwable;
                            })
                            .doOnCancel(() -> diagnosticsFactory.merge(state.getDiagnosticsContextSnapshot()));
    }

    private  Flux> createQueryInternal(
            DiagnosticsClientContext diagnosticsClientContext,
            String resourceLink,
            SqlQuerySpec sqlQuery,
            CosmosQueryRequestOptions options,
            Class klass,
            ResourceType resourceTypeEnum,
            IDocumentQueryClient queryClient,
            UUID activityId,
            final AtomicBoolean isQueryCancelledOnTimeout) {

        Flux> executionContext =
            DocumentQueryExecutionContextFactory
                .createDocumentQueryExecutionContextAsync(diagnosticsClientContext, queryClient, resourceTypeEnum, klass, sqlQuery,
                                                          options, resourceLink, false, activityId,
                                                          Configs.isQueryPlanCachingEnabled(), queryPlanCache, isQueryCancelledOnTimeout);

        AtomicBoolean isFirstResponse = new AtomicBoolean(true);
        return executionContext.flatMap(iDocumentQueryExecutionContext -> {
            QueryInfo queryInfo = null;
            if (iDocumentQueryExecutionContext instanceof PipelinedQueryExecutionContextBase) {
                queryInfo = ((PipelinedQueryExecutionContextBase) iDocumentQueryExecutionContext).getQueryInfo();
            }

            QueryInfo finalQueryInfo = queryInfo;
            Flux> feedResponseFlux = iDocumentQueryExecutionContext.executeAsync()
                .map(tFeedResponse -> {
                    if (finalQueryInfo != null) {
                        if (finalQueryInfo.hasSelectValue()) {
                            ModelBridgeInternal
                                .addQueryInfoToFeedResponse(tFeedResponse, finalQueryInfo);
                        }

                        if (isFirstResponse.compareAndSet(true, false)) {
                            ModelBridgeInternal.addQueryPlanDiagnosticsContextToFeedResponse(tFeedResponse,
                                finalQueryInfo.getQueryPlanDiagnosticsContext());
                        }
                    }
                    return tFeedResponse;
                });

            RequestOptions requestOptions = options == null? null : ImplementationBridgeHelpers
                .CosmosQueryRequestOptionsHelper
                .getCosmosQueryRequestOptionsAccessor()
                .toRequestOptions(options);

            CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig =
                getEndToEndOperationLatencyPolicyConfig(requestOptions, resourceTypeEnum, OperationType.Query);

            if (endToEndPolicyConfig != null && endToEndPolicyConfig.isEnabled()) {
                return getFeedResponseFluxWithTimeout(
                    feedResponseFlux,
                    endToEndPolicyConfig,
                    options,
                    isQueryCancelledOnTimeout,
                    diagnosticsClientContext);
            }

            return feedResponseFlux;
            // concurrency is set to Queues.SMALL_BUFFER_SIZE to
            // maximize the IDocumentQueryExecutionContext publisher instances to subscribe to concurrently
            // prefetch is set to 1 to minimize the no. prefetched pages (result of merged executeAsync invocations)
        }, Queues.SMALL_BUFFER_SIZE, 1);
    }

    private static void applyExceptionToMergedDiagnosticsForQuery(
        CosmosQueryRequestOptions requestOptions,
        CosmosException exception,
        DiagnosticsClientContext diagnosticsClientContext) {

         CosmosDiagnostics mostRecentlyCreatedDiagnostics =
            diagnosticsClientContext.getMostRecentlyCreatedDiagnostics();

        if (mostRecentlyCreatedDiagnostics != null) {
            // When reaching here, it means the query(s) has timed out based on the e2e timeout config policy
            // Since all the underlying ongoing query requests will all timed out
            // We just use the last cosmosDiagnostics in the scoped diagnostics factory to populate the exception
            BridgeInternal.setCosmosDiagnostics(
                exception,
                mostRecentlyCreatedDiagnostics);
        } else {
            List cancelledRequestDiagnostics =
                qryOptAccessor
                    .getCancelledRequestDiagnosticsTracker(requestOptions);
            // if there is any cancelled requests, collect cosmos diagnostics
            if (cancelledRequestDiagnostics != null && !cancelledRequestDiagnostics.isEmpty()) {
                // combine all the cosmos diagnostics
                CosmosDiagnostics aggregratedCosmosDiagnostics =
                    cancelledRequestDiagnostics
                        .stream()
                        .reduce((first, toBeMerged) -> {
                            ClientSideRequestStatistics clientSideRequestStatistics =
                                ImplementationBridgeHelpers
                                    .CosmosDiagnosticsHelper
                                    .getCosmosDiagnosticsAccessor()
                                    .getClientSideRequestStatisticsRaw(first);

                            ClientSideRequestStatistics toBeMergedClientSideRequestStatistics =
                                ImplementationBridgeHelpers
                                    .CosmosDiagnosticsHelper
                                    .getCosmosDiagnosticsAccessor()
                                    .getClientSideRequestStatisticsRaw(first);

                            if (clientSideRequestStatistics == null) {
                                return toBeMerged;
                            } else {
                                clientSideRequestStatistics.mergeClientSideRequestStatistics(toBeMergedClientSideRequestStatistics);
                                return first;
                            }
                        })
                        .get();

                BridgeInternal.setCosmosDiagnostics(exception, aggregratedCosmosDiagnostics);
            }
        }
    }

    private static  Flux> getFeedResponseFluxWithTimeout(
        Flux> feedResponseFlux,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        CosmosQueryRequestOptions requestOptions,
        final AtomicBoolean isQueryCancelledOnTimeout,
        DiagnosticsClientContext diagnosticsClientContext) {

        Duration endToEndTimeout = endToEndPolicyConfig.getEndToEndOperationTimeout();

        if (endToEndTimeout.isNegative()) {
            return feedResponseFlux
                .timeout(endToEndTimeout)
                .onErrorMap(throwable -> {
                    if (throwable instanceof TimeoutException) {
                        CosmosException cancellationException = getNegativeTimeoutException(null, endToEndTimeout);
                        cancellationException.setStackTrace(throwable.getStackTrace());

                        isQueryCancelledOnTimeout.set(true);

                        applyExceptionToMergedDiagnosticsForQuery(
                            requestOptions, cancellationException, diagnosticsClientContext);

                        return cancellationException;
                    }
                    return throwable;
                });
        }

        return feedResponseFlux
            .timeout(endToEndTimeout)
            .onErrorMap(throwable -> {
                if (throwable instanceof TimeoutException) {
                    CosmosException exception = new OperationCancelledException();
                    exception.setStackTrace(throwable.getStackTrace());

                    isQueryCancelledOnTimeout.set(true);

                    applyExceptionToMergedDiagnosticsForQuery(requestOptions, exception, diagnosticsClientContext);

                    return exception;
                }
                return throwable;
            });
    }

    @Override
    public Flux> queryDatabases(String query, QueryFeedOperationState state) {
        return queryDatabases(new SqlQuerySpec(query), state);
    }


    @Override
    public Flux> queryDatabases(SqlQuerySpec querySpec, QueryFeedOperationState state) {
        return createQuery(Paths.DATABASES_ROOT, querySpec, state, Database.class, ResourceType.Database);
    }

    @Override
    public Mono> createCollection(String databaseLink,
                                                                       DocumentCollection collection, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> this.createCollectionInternal(databaseLink, collection, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> createCollectionInternal(String databaseLink,
                                                                                DocumentCollection collection, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            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());
            validateResource(collection);

            String path = Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.DocumentCollection, OperationType.Create);

            Instant serializationStartTimeUTC = Instant.now();
            ByteBuffer byteBuffer = collection.serializeJsonToByteBuffer(CosmosItemSerializer.DEFAULT_SERIALIZER, null, false);
            Instant serializationEndTimeUTC = Instant.now();
            SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics = new SerializationDiagnosticsContext.SerializationDiagnostics(
                serializationStartTimeUTC,
                serializationEndTimeUTC,
                SerializationDiagnosticsContext.SerializationType.CONTAINER_SERIALIZATION);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Create, ResourceType.DocumentCollection, path, byteBuffer, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            SerializationDiagnosticsContext serializationDiagnosticsContext = BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);
            if (serializationDiagnosticsContext != null) {
                serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
            }

            return this.create(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, DocumentCollection.class))
                       .doOnNext(resourceResponse -> {
                    // set the session token
                    this.sessionContainer.setSessionToken(
                        request,
                        resourceResponse.getResource().getResourceId(),
                        getAltLink(resourceResponse.getResource()),
                        resourceResponse.getResponseHeaders());
                });
        } catch (Exception e) {
            logger.debug("Failure in creating a collection. due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> replaceCollection(DocumentCollection collection,
                                                                        RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replaceCollectionInternal(collection, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> replaceCollectionInternal(DocumentCollection collection,
                                                                                 RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (collection == null) {
                throw new IllegalArgumentException("collection");
            }

            logger.debug("Replacing a Collection. id: [{}]", collection.getId());
            validateResource(collection);

            String path = Utils.joinPath(collection.getSelfLink(), null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.DocumentCollection, OperationType.Replace);
            Instant serializationStartTimeUTC = Instant.now();
            ByteBuffer byteBuffer = collection.serializeJsonToByteBuffer(CosmosItemSerializer.DEFAULT_SERIALIZER, null, false);
            Instant serializationEndTimeUTC = Instant.now();
            SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics = new SerializationDiagnosticsContext.SerializationDiagnostics(
                serializationStartTimeUTC,
                serializationEndTimeUTC,
                SerializationDiagnosticsContext.SerializationType.CONTAINER_SERIALIZATION);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Replace, ResourceType.DocumentCollection, path, byteBuffer, requestHeaders, options);

            // TODO: .Net has some logic for updating session token which we don't
            // have here
            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            SerializationDiagnosticsContext serializationDiagnosticsContext = BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);
            if (serializationDiagnosticsContext != null) {
                serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
            }

            return this.replace(request, retryPolicyInstance).map(response -> toResourceResponse(response, DocumentCollection.class))
                .doOnNext(resourceResponse -> {
                    if (resourceResponse.getResource() != null) {
                        // set the session token
                        this.sessionContainer.setSessionToken(
                            request,
                            resourceResponse.getResource().getResourceId(),
                            getAltLink(resourceResponse.getResource()),
                            resourceResponse.getResponseHeaders());
                    }
                });

        } catch (Exception e) {
            logger.debug("Failure in replacing a collection. due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> deleteCollection(String collectionLink,
                                                                       RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteCollectionInternal(collectionLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> deleteCollectionInternal(String collectionLink,
                                                                                RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (StringUtils.isEmpty(collectionLink)) {
                throw new IllegalArgumentException("collectionLink");
            }

            logger.debug("Deleting a Collection. collectionLink: [{}]", collectionLink);
            String path = Utils.joinPath(collectionLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.DocumentCollection, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.DocumentCollection, path, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, DocumentCollection.class));

        } catch (Exception e) {
            logger.debug("Failure in deleting a collection, due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    private Mono delete(RxDocumentServiceRequest request, DocumentClientRetryPolicy documentClientRetryPolicy, OperationContextAndListenerTuple operationContextAndListenerTuple) {
        return populateHeadersAsync(request, RequestVerb.DELETE)
            .flatMap(requestPopulated -> {
                if (documentClientRetryPolicy.getRetryContext() != null && documentClientRetryPolicy.getRetryContext().getRetryCount() > 0) {
                    documentClientRetryPolicy.getRetryContext().updateEndTime();
                }

                return getStoreProxy(requestPopulated).processMessage(requestPopulated, operationContextAndListenerTuple);
            });
    }

    private Mono deleteAllItemsByPartitionKey(RxDocumentServiceRequest request, DocumentClientRetryPolicy documentClientRetryPolicy, OperationContextAndListenerTuple operationContextAndListenerTuple) {
        return populateHeadersAsync(request, RequestVerb.POST)
            .flatMap(requestPopulated -> {
                RxStoreModel storeProxy = this.getStoreProxy(requestPopulated);
                if (documentClientRetryPolicy.getRetryContext() != null && documentClientRetryPolicy.getRetryContext().getRetryCount() > 0) {
                    documentClientRetryPolicy.getRetryContext().updateEndTime();
                }

                return storeProxy.processMessage(requestPopulated, operationContextAndListenerTuple);
            });
    }

    private Mono read(RxDocumentServiceRequest request, DocumentClientRetryPolicy documentClientRetryPolicy) {
        return populateHeadersAsync(request, RequestVerb.GET)
            .flatMap(requestPopulated -> {
                if (documentClientRetryPolicy.getRetryContext() != null && documentClientRetryPolicy.getRetryContext().getRetryCount() > 0) {
                    documentClientRetryPolicy.getRetryContext().updateEndTime();
                }

                return getStoreProxy(requestPopulated).processMessage(requestPopulated);
                });
    }

    Mono readFeed(RxDocumentServiceRequest request) {
        return populateHeadersAsync(request, RequestVerb.GET)
            .flatMap(requestPopulated -> getStoreProxy(requestPopulated).processMessage(requestPopulated));
    }

    private Mono query(RxDocumentServiceRequest request) {
        // If endToEndOperationLatencyPolicy is set in the query request options,
        // then it should have been populated into request context already
        // otherwise set them here with the client level policy

        return populateHeadersAsync(request, RequestVerb.POST)
            .flatMap(requestPopulated ->
                this.getStoreProxy(requestPopulated).processMessage(requestPopulated)
                    .map(response -> {
                            this.captureSessionToken(requestPopulated, response);
                            return response;
                        }
                    ));
    }

    @Override
    public Mono> readCollection(String collectionLink,
                                                                     RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readCollectionInternal(collectionLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readCollectionInternal(String collectionLink,
                                                                              RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {

        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {
            if (StringUtils.isEmpty(collectionLink)) {
                throw new IllegalArgumentException("collectionLink");
            }

            logger.debug("Reading a Collection. collectionLink: [{}]", collectionLink);
            String path = Utils.joinPath(collectionLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.DocumentCollection, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.DocumentCollection, path, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }
            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, DocumentCollection.class));
        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in reading a collection, due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readCollections(String databaseLink, QueryFeedOperationState state) {

        if (StringUtils.isEmpty(databaseLink)) {
            throw new IllegalArgumentException("databaseLink");
        }

        return nonDocumentReadFeed(state, ResourceType.DocumentCollection, DocumentCollection.class,
                Utils.joinPath(databaseLink, Paths.COLLECTIONS_PATH_SEGMENT));
    }

    @Override
    public Flux> queryCollections(String databaseLink, String query,
                                                                   QueryFeedOperationState state) {
        return createQuery(databaseLink, new SqlQuerySpec(query), state, DocumentCollection.class, ResourceType.DocumentCollection);
    }

    @Override
    public Flux> queryCollections(String databaseLink,
                                                                         SqlQuerySpec querySpec, QueryFeedOperationState state) {
        return createQuery(databaseLink, querySpec, state, DocumentCollection.class, ResourceType.DocumentCollection);
    }

    private static String serializeProcedureParams(List objectArray) {
        String[] stringArray = new String[objectArray.size()];

        for (int i = 0; i < objectArray.size(); ++i) {
            Object object = objectArray.get(i);
            if (object instanceof JsonSerializable) {
                stringArray[i] = ((JsonSerializable) object).toJson();
            } else {

                // POJO, ObjectNode, number, STRING or Boolean
                try {
                    stringArray[i] = mapper.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, ","));
    }

    private 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.");
            }
        }
    }

    private Map getRequestHeaders(RequestOptions options, ResourceType resourceType, OperationType operationType) {
        Map headers = new HashMap<>();

        if (this.useMultipleWriteLocations) {
            headers.put(HttpConstants.HttpHeaders.ALLOW_TENTATIVE_WRITES, Boolean.TRUE.toString());
        }

        if (consistencyLevel != null) {
            headers.put(HttpConstants.HttpHeaders.CONSISTENCY_LEVEL, consistencyLevel.toString());
        }

        if (options == null) {
            //  Corner case, if options are null, then just check this flag from CosmosClientBuilder
            //  If content response on write is not enabled, and operation is document write - then add minimal prefer header
            //  Otherwise, don't add this header, which means return the full response
            if (!this.contentResponseOnWriteEnabled && resourceType.equals(ResourceType.Document) && operationType.isWriteOperation()) {
                headers.put(HttpConstants.HttpHeaders.PREFER, HttpConstants.HeaderValues.PREFER_RETURN_MINIMAL);
            }
            return headers;
        }

        Map customOptions = options.getHeaders();
        if (customOptions != null) {
            headers.putAll(customOptions);
        }

        boolean contentResponseOnWriteEnabled = this.contentResponseOnWriteEnabled;
        //  If options has contentResponseOnWriteEnabled set to true / false, override the value from CosmosClientBuilder
        if (options.isContentResponseOnWriteEnabled() != null) {
            contentResponseOnWriteEnabled = options.isContentResponseOnWriteEnabled();
        }

        //  If content response on write is not enabled, and operation is document write - then add minimal prefer header
        //  Otherwise, don't add this header, which means return the full response
        if (!contentResponseOnWriteEnabled && resourceType.equals(ResourceType.Document) && operationType.isWriteOperation()) {
            headers.put(HttpConstants.HttpHeaders.PREFER, HttpConstants.HeaderValues.PREFER_RETURN_MINIMAL);
        }

        if (options.getIfMatchETag() != null) {
            headers.put(HttpConstants.HttpHeaders.IF_MATCH, options.getIfMatchETag());
        }

        if (options.getIfNoneMatchETag() != null) {
            headers.put(HttpConstants.HttpHeaders.IF_NONE_MATCH, options.getIfNoneMatchETag());
        }

        if (options.getConsistencyLevel() != null) {
            headers.put(HttpConstants.HttpHeaders.CONSISTENCY_LEVEL, options.getConsistencyLevel().toString());
        }

        if (options.getIndexingDirective() != null) {
            headers.put(HttpConstants.HttpHeaders.INDEXING_DIRECTIVE, options.getIndexingDirective().toString());
        }

        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 (!Strings.isNullOrEmpty(options.getSessionToken())) {
            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.getOfferThroughput() == null) {
            if (options.getThroughputProperties() != null) {
                Offer offer = ModelBridgeInternal.getOfferFromThroughputProperties(options.getThroughputProperties());
                final OfferAutoscaleSettings offerAutoscaleSettings = offer.getOfferAutoScaleSettings();
                OfferAutoscaleAutoUpgradeProperties autoscaleAutoUpgradeProperties = null;
                if (offerAutoscaleSettings != null) {
                    autoscaleAutoUpgradeProperties
                        = offer.getOfferAutoScaleSettings().getAutoscaleAutoUpgradeProperties();
                }
                if (offer.hasOfferThroughput() &&
                    (offerAutoscaleSettings != null && offerAutoscaleSettings.getMaxThroughput() >= 0 ||
                        autoscaleAutoUpgradeProperties != null &&
                            autoscaleAutoUpgradeProperties
                                .getAutoscaleThroughputProperties()
                                .getIncrementPercent() >= 0)) {
                    throw new IllegalArgumentException("Autoscale provisioned throughput can not be configured with "
                        + "fixed offer");
                }

                if (offer.hasOfferThroughput()) {
                    headers.put(HttpConstants.HttpHeaders.OFFER_THROUGHPUT, String.valueOf(offer.getThroughput()));
                } else if (offer.getOfferAutoScaleSettings() != null) {
                    headers.put(HttpConstants.HttpHeaders.OFFER_AUTOPILOT_SETTINGS,
                        offer.getOfferAutoScaleSettings().toJson());
                }
            }
        }

        if (options.isQuotaInfoEnabled()) {
            headers.put(HttpConstants.HttpHeaders.POPULATE_QUOTA_INFO, String.valueOf(true));
        }

        if (options.isScriptLoggingEnabled()) {
            headers.put(HttpConstants.HttpHeaders.SCRIPT_ENABLE_LOGGING, String.valueOf(true));
        }

        if (options.getDedicatedGatewayRequestOptions() != null) {
            if (options.getDedicatedGatewayRequestOptions().getMaxIntegratedCacheStaleness() != null) {
                headers.put(HttpConstants.HttpHeaders.DEDICATED_GATEWAY_PER_REQUEST_CACHE_STALENESS,
                    String.valueOf(Utils.getMaxIntegratedCacheStalenessInMillis(options.getDedicatedGatewayRequestOptions())));
            }
            if (options.getDedicatedGatewayRequestOptions().isIntegratedCacheBypassed()) {
                headers.put(HttpConstants.HttpHeaders.DEDICATED_GATEWAY_PER_REQUEST_BYPASS_CACHE,
                    String.valueOf(options.getDedicatedGatewayRequestOptions().isIntegratedCacheBypassed()));
            }
        }

        return headers;
    }

    public IRetryPolicyFactory getResetSessionTokenRetryPolicy() {
        return this.resetSessionTokenRetryPolicy;
    }

    private Mono addPartitionKeyInformation(RxDocumentServiceRequest request,
                                                                      ByteBuffer contentAsByteBuffer,
                                                                      Document document,
                                                                      RequestOptions options) {

        Mono> collectionObs = this.collectionCache.resolveCollectionAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request);
        return collectionObs
                .map(collectionValueHolder -> {
                    addPartitionKeyInformation(request, contentAsByteBuffer, document, options, collectionValueHolder.v, null);
                    return request;
                });
    }

    private Mono addPartitionKeyInformation(RxDocumentServiceRequest request,
                                                                      ByteBuffer contentAsByteBuffer,
                                                                      Object document,
                                                                      RequestOptions options,
                                                                      Mono> collectionObs,
                                                                      PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        return collectionObs.map(collectionValueHolder -> {
            addPartitionKeyInformation(request, contentAsByteBuffer, document, options, collectionValueHolder.v, pointOperationContextForCircuitBreaker);
            return request;
        });
    }

    private void addPartitionKeyInformation(RxDocumentServiceRequest request,
                                            ByteBuffer contentAsByteBuffer,
                                            Object objectDoc, RequestOptions options,
                                            DocumentCollection collection,
                                            PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        PartitionKeyDefinition partitionKeyDefinition = collection.getPartitionKey();

        PartitionKeyInternal partitionKeyInternal = null;
        if (options != null && options.getPartitionKey() != null && options.getPartitionKey().equals(PartitionKey.NONE)){
            partitionKeyInternal = ModelBridgeInternal.getNonePartitionKey(partitionKeyDefinition);
        } else if (options != null && options.getPartitionKey() != null) {
            partitionKeyInternal = BridgeInternal.getPartitionKeyInternal(options.getPartitionKey());
        } else 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 (contentAsByteBuffer != null || objectDoc != null) {
            InternalObjectNode internalObjectNode;
            if (objectDoc instanceof InternalObjectNode) {
                internalObjectNode = (InternalObjectNode) objectDoc;
            } else if (objectDoc instanceof ObjectNode) {
                internalObjectNode = new InternalObjectNode((ObjectNode)objectDoc);
            } else if (contentAsByteBuffer != null) {
                contentAsByteBuffer.rewind();
                internalObjectNode = new InternalObjectNode(contentAsByteBuffer);
            } else {
                //  This is a safety check, this should not happen ever.
                //  If it does, it is a SDK bug
                throw new IllegalStateException("ContentAsByteBuffer and objectDoc are null");
            }

            Instant serializationStartTime = Instant.now();
            partitionKeyInternal =  PartitionKeyHelper.extractPartitionKeyValueFromDocument(internalObjectNode, partitionKeyDefinition);
            Instant serializationEndTime = Instant.now();
            SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics = new SerializationDiagnosticsContext.SerializationDiagnostics(
                serializationStartTime,
                serializationEndTime,
                SerializationDiagnosticsContext.SerializationType.PARTITION_KEY_FETCH_SERIALIZATION
            );

            SerializationDiagnosticsContext serializationDiagnosticsContext = BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);
            if (serializationDiagnosticsContext != null) {
                serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
            } else if (pointOperationContextForCircuitBreaker != null) {
                serializationDiagnosticsContext = pointOperationContextForCircuitBreaker.getSerializationDiagnosticsContext();

                if (serializationDiagnosticsContext != null) {
                    serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
                }
            }

        } else {
            throw new UnsupportedOperationException("PartitionKey value must be supplied for this operation.");
        }

        request.setPartitionKeyInternal(partitionKeyInternal);
        request.setPartitionKeyDefinition(partitionKeyDefinition);
        request.getHeaders().put(HttpConstants.HttpHeaders.PARTITION_KEY, Utils.escapeNonAscii(partitionKeyInternal.toJson()));
    }

    private Mono>> getCreateDocumentRequest(DocumentClientRetryPolicy requestRetryPolicy,
                                                                           String documentCollectionLink,
                                                                           Object document,
                                                                           RequestOptions options,
                                                                           boolean disableAutomaticIdGeneration,
                                                                           OperationType operationType,
                                                                           DiagnosticsClientContext clientContextOverride,
                                                                           PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        if (StringUtils.isEmpty(documentCollectionLink)) {
            throw new IllegalArgumentException("documentCollectionLink");
        }
        if (document == null) {
            throw new IllegalArgumentException("document");
        }

        Instant serializationStartTimeUTC = Instant.now();
        String trackingId = null;
        if (options != null) {
            trackingId = options.getTrackingId();
        }
        ByteBuffer content = InternalObjectNode.serializeJsonToByteBuffer(document, options.getEffectiveItemSerializer(), trackingId, true);
        Instant serializationEndTimeUTC = Instant.now();

        SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics = new SerializationDiagnosticsContext.SerializationDiagnostics(
            serializationStartTimeUTC,
            serializationEndTimeUTC,
            SerializationDiagnosticsContext.SerializationType.ITEM_SERIALIZATION);

        String path = Utils.joinPath(documentCollectionLink, Paths.DOCUMENTS_PATH_SEGMENT);
        Map requestHeaders = this.getRequestHeaders(options, ResourceType.Document, operationType);

        RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
            getEffectiveClientContext(clientContextOverride),
            operationType, ResourceType.Document, path, requestHeaders, options, content);

        if (operationType.isWriteOperation() &&  options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled()) {
            request.setNonIdempotentWriteRetriesEnabled(true);
        }

        if( options != null) {
            options.getMarkE2ETimeoutInRequestContextCallbackHook().set(
                () -> request.requestContext.setIsRequestCancelledOnTimeout(new AtomicBoolean(true)));
            request.requestContext.setExcludeRegions(options.getExcludedRegions());
            request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());
        }

        SerializationDiagnosticsContext serializationDiagnosticsContext = BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);
        if (serializationDiagnosticsContext != null) {
            serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
        }

        if (requestRetryPolicy != null) {
            requestRetryPolicy.onBeforeSendRequest(request);
        }

        Mono> collectionObs = this.collectionCache.resolveCollectionAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request);
        return addPartitionKeyInformation(request, content, document, options, collectionObs, pointOperationContextForCircuitBreaker)
            .zipWith(collectionObs);
    }

    private Mono getBatchDocumentRequest(DocumentClientRetryPolicy requestRetryPolicy,
                                                                   String documentCollectionLink,
                                                                   ServerBatchRequest serverBatchRequest,
                                                                   RequestOptions options,
                                                                   boolean disableAutomaticIdGeneration) {

        checkArgument(StringUtils.isNotEmpty(documentCollectionLink), "expected non empty documentCollectionLink");
        checkNotNull(serverBatchRequest, "expected non null serverBatchRequest");

        Instant serializationStartTimeUTC = Instant.now();
        ByteBuffer content = ByteBuffer.wrap(Utils.getUTF8Bytes(serverBatchRequest.getRequestBody()));
        Instant serializationEndTimeUTC = Instant.now();

        SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics = new SerializationDiagnosticsContext.SerializationDiagnostics(
            serializationStartTimeUTC,
            serializationEndTimeUTC,
            SerializationDiagnosticsContext.SerializationType.ITEM_SERIALIZATION);

        String path = Utils.joinPath(documentCollectionLink, Paths.DOCUMENTS_PATH_SEGMENT);
        Map requestHeaders = this.getRequestHeaders(options, ResourceType.Document, OperationType.Batch);

        RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
            this,
            OperationType.Batch,
            ResourceType.Document,
            path,
            requestHeaders,
            options,
            content);

        if (options != null) {
            options.getMarkE2ETimeoutInRequestContextCallbackHook().set(
                () -> request.requestContext.setIsRequestCancelledOnTimeout(new AtomicBoolean(true)));
            request.requestContext.setExcludeRegions(options.getExcludedRegions());
            request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());
        }

        SerializationDiagnosticsContext serializationDiagnosticsContext = BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);

        if (serializationDiagnosticsContext != null) {
            serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
        }

        if (options != null) {
            request.requestContext.setExcludeRegions(options.getExcludedRegions());
            request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());
        }

        // note: calling onBeforeSendRequest is a cheap operation which injects a CosmosDiagnostics
        // instance into 'request' amongst other things - this way metadataDiagnosticsContext is not
        // null and can be used for metadata-related telemetry (partition key range, container and server address lookups)
        if (requestRetryPolicy != null) {
            requestRetryPolicy.onBeforeSendRequest(request);
        }

        MetadataDiagnosticsContext metadataDiagnosticsContext = BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics);

        request.requestContext.setPointOperationContext(
            new PointOperationContextForCircuitBreaker(
                new AtomicBoolean(false),
                false,
                documentCollectionLink,
                serializationDiagnosticsContext));

        return this.collectionCache.resolveCollectionAsync(metadataDiagnosticsContext, request)
            .flatMap(documentCollectionValueHolder -> {

                if (documentCollectionValueHolder == null || documentCollectionValueHolder.v == null) {
                    return Mono.error(new IllegalStateException("documentCollectionValueHolder or documentCollectionValueHolder.v cannot be null"));
                }

                return this.partitionKeyRangeCache.tryLookupAsync(metadataDiagnosticsContext, documentCollectionValueHolder.v.getResourceId(), null, null)
                    .flatMap(collectionRoutingMapValueHolder -> {

                        if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) {
                            return Mono.error(new IllegalStateException("collectionRoutingMapValueHolder or collectionRoutingMapValueHolder.v cannot be null"));
                        }

                        addBatchHeaders(request, serverBatchRequest, documentCollectionValueHolder.v);

                        if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(request) && options != null) {
                            options.setPartitionKeyDefinition(documentCollectionValueHolder.v.getPartitionKey());
                            addPartitionLevelUnavailableRegionsForRequest(request, options, collectionRoutingMapValueHolder.v, requestRetryPolicy);
                        }

                        return Mono.just(request);
                    });
            });
    }

    private RxDocumentServiceRequest addBatchHeaders(RxDocumentServiceRequest request,
                                                     ServerBatchRequest serverBatchRequest,
                                                     DocumentCollection collection) {

        if(serverBatchRequest instanceof SinglePartitionKeyServerBatchRequest) {

            PartitionKey partitionKey = ((SinglePartitionKeyServerBatchRequest) serverBatchRequest).getPartitionKeyValue();
            PartitionKeyInternal partitionKeyInternal;

            if (partitionKey.equals(PartitionKey.NONE)) {
                PartitionKeyDefinition partitionKeyDefinition = collection.getPartitionKey();
                partitionKeyInternal = ModelBridgeInternal.getNonePartitionKey(partitionKeyDefinition);
            } else {
                // Partition key is always non-null
                partitionKeyInternal = BridgeInternal.getPartitionKeyInternal(partitionKey);
            }

            request.setPartitionKeyInternal(partitionKeyInternal);
            request.getHeaders().put(HttpConstants.HttpHeaders.PARTITION_KEY, Utils.escapeNonAscii(partitionKeyInternal.toJson()));
        } else if(serverBatchRequest instanceof PartitionKeyRangeServerBatchRequest) {
            request.setPartitionKeyRangeIdentity(new PartitionKeyRangeIdentity(((PartitionKeyRangeServerBatchRequest) serverBatchRequest).getPartitionKeyRangeId()));
        } else {
            throw new UnsupportedOperationException("Unknown Server request.");
        }

        request.getHeaders().put(HttpConstants.HttpHeaders.IS_BATCH_REQUEST, Boolean.TRUE.toString());
        request.getHeaders().put(HttpConstants.HttpHeaders.IS_BATCH_ATOMIC, String.valueOf(serverBatchRequest.isAtomicBatch()));
        request.getHeaders().put(HttpConstants.HttpHeaders.SHOULD_BATCH_CONTINUE_ON_ERROR, String.valueOf(serverBatchRequest.isShouldContinueOnError()));

        request.setPartitionKeyDefinition(collection.getPartitionKey());
        request.setNumberOfItemsInBatchRequest(serverBatchRequest.getOperations().size());

        return request;
    }

    /**
     * NOTE: Caller needs to consume it by subscribing to this Mono in order for the request to populate headers
     * @param request request to populate headers to
     * @param httpMethod http method
     * @return Mono, which on subscription will populate the headers in the request passed in the argument.
     */
    public Mono populateHeadersAsync(RxDocumentServiceRequest request, RequestVerb httpMethod) {
        request.getHeaders().put(HttpConstants.HttpHeaders.X_DATE, Utils.nowAsRFC1123());
        if (this.masterKeyOrResourceToken != null || this.resourceTokensMap != null
            || this.cosmosAuthorizationTokenResolver != null || this.credential != null) {
            String resourceName = request.getResourceAddress();

            String authorization = this.getUserAuthorizationToken(
                resourceName, request.getResourceType(), httpMethod, request.getHeaders(),
                    AuthorizationTokenType.PrimaryMasterKey, request.properties);
            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 (this.apiType != null)   {
            request.getHeaders().put(HttpConstants.HttpHeaders.API_TYPE, this.apiType.toString());
        }

        this.populateCapabilitiesHeader(request);

        if ((RequestVerb.POST.equals(httpMethod) || RequestVerb.PUT.equals(httpMethod))
                && !request.getHeaders().containsKey(HttpConstants.HttpHeaders.CONTENT_TYPE)) {
            request.getHeaders().put(HttpConstants.HttpHeaders.CONTENT_TYPE, RuntimeConstants.MediaTypes.JSON);
        }

        if (RequestVerb.PATCH.equals(httpMethod) &&
            !request.getHeaders().containsKey(HttpConstants.HttpHeaders.CONTENT_TYPE)) {
            request.getHeaders().put(HttpConstants.HttpHeaders.CONTENT_TYPE, RuntimeConstants.MediaTypes.JSON_PATCH);
        }

        if (!request.getHeaders().containsKey(HttpConstants.HttpHeaders.ACCEPT)) {
            request.getHeaders().put(HttpConstants.HttpHeaders.ACCEPT, RuntimeConstants.MediaTypes.JSON);
        }

        MetadataDiagnosticsContext metadataDiagnosticsCtx =
            BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics);

        if (this.requiresFeedRangeFiltering(request)) {
            return request.getFeedRange()
                          .populateFeedRangeFilteringHeaders(
                              this.getPartitionKeyRangeCache(),
                              request,
                              this.collectionCache
                                  .resolveCollectionAsync(metadataDiagnosticsCtx, request)
                                  .flatMap(documentCollectionValueHolder -> {

                                      if (documentCollectionValueHolder.v != null) {
                                          request.setPartitionKeyDefinition(documentCollectionValueHolder.v.getPartitionKey());
                                      }

                                      return Mono.just(documentCollectionValueHolder);
                                  })
                          )
                          .flatMap(this::populateAuthorizationHeader);
        }

        return this.populateAuthorizationHeader(request);
    }

    private void populateCapabilitiesHeader(RxDocumentServiceRequest request) {
        if (!request.getHeaders().containsKey(HttpConstants.HttpHeaders.SDK_SUPPORTED_CAPABILITIES)) {
            request
                .getHeaders()
                .put(HttpConstants.HttpHeaders.SDK_SUPPORTED_CAPABILITIES, HttpConstants.SDKSupportedCapabilities.SUPPORTED_CAPABILITIES);
        }
    }

    private boolean requiresFeedRangeFiltering(RxDocumentServiceRequest request) {
        if (request.getResourceType() != ResourceType.Document &&
                request.getResourceType() != ResourceType.Conflict) {
            return false;
        }

        if (request.hasFeedRangeFilteringBeenApplied()) {
            return false;
        }

        switch (request.getOperationType()) {
            case ReadFeed:
            case Query:
            case SqlQuery:
                return request.getFeedRange() != null;
            default:
                return false;
        }
    }

    @Override
    public Mono populateAuthorizationHeader(RxDocumentServiceRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("request");
        }

        if (this.authorizationTokenType == AuthorizationTokenType.AadToken) {
            return AadTokenAuthorizationHelper.getAuthorizationToken(this.tokenCredentialCache)
                .map(authorization -> {
                    request.getHeaders().put(HttpConstants.HttpHeaders.AUTHORIZATION, authorization);
                    return request;
                });
        } else {
            return Mono.just(request);
        }
    }

    @Override
    public Mono populateAuthorizationHeader(HttpHeaders httpHeaders) {
        if (httpHeaders == null) {
            throw new IllegalArgumentException("httpHeaders");
        }

        if (this.authorizationTokenType == AuthorizationTokenType.AadToken) {
            return AadTokenAuthorizationHelper.getAuthorizationToken(this.tokenCredentialCache)
                .map(authorization -> {
                    httpHeaders.set(HttpConstants.HttpHeaders.AUTHORIZATION, authorization);
                    return httpHeaders;
                });
        }

        return Mono.just(httpHeaders);
    }

    @Override
    public AuthorizationTokenType getAuthorizationTokenType() {
        return this.authorizationTokenType;
    }

    @Override
    public String getUserAuthorizationToken(String resourceName,
                                            ResourceType resourceType,
                                            RequestVerb requestVerb,
                                            Map headers,
                                            AuthorizationTokenType tokenType,
                                            Map properties) {

        if (this.cosmosAuthorizationTokenResolver != null) {
            return this.cosmosAuthorizationTokenResolver.getAuthorizationToken(requestVerb.toUpperCase(), resourceName, this.resolveCosmosResourceType(resourceType).toString(),
                    properties != null ? Collections.unmodifiableMap(properties) : null);
        } else if (credential != null) {
            return this.authorizationTokenProvider.generateKeyAuthorizationSignature(requestVerb, resourceName,
                    resourceType, headers);
        } else if (masterKeyOrResourceToken != null && hasAuthKeyResourceToken && resourceTokensMap == null) {
            return masterKeyOrResourceToken;
        } else {
            assert resourceTokensMap != null;
            if(resourceType.equals(ResourceType.DatabaseAccount)) {
                return this.firstResourceTokenFromPermissionFeed;
            }

            return ResourceTokenAuthorizationHelper.getAuthorizationTokenUsingResourceTokens(resourceTokensMap, requestVerb, resourceName, headers);
        }
    }

    private CosmosResourceType resolveCosmosResourceType(ResourceType resourceType) {
        CosmosResourceType cosmosResourceType =
            ModelBridgeInternal.fromServiceSerializedFormat(resourceType.toString());
        if (cosmosResourceType == null) {
            return CosmosResourceType.SYSTEM;
        }
        return cosmosResourceType;
    }

    void captureSessionToken(RxDocumentServiceRequest request, RxDocumentServiceResponse response) {
        this.sessionContainer.setSessionToken(request, response.getResponseHeaders());
    }

    private Mono create(RxDocumentServiceRequest request,
                                                   DocumentClientRetryPolicy documentClientRetryPolicy,
                                                   OperationContextAndListenerTuple operationContextAndListenerTuple) {
        return populateHeadersAsync(request, RequestVerb.POST)
            .flatMap(requestPopulated -> {
                RxStoreModel storeProxy = this.getStoreProxy(requestPopulated);
                if (documentClientRetryPolicy.getRetryContext() != null && documentClientRetryPolicy.getRetryContext().getRetryCount() > 0) {
                    documentClientRetryPolicy.getRetryContext().updateEndTime();
                }

                return storeProxy.processMessage(requestPopulated, operationContextAndListenerTuple);
            });
    }

    private Mono upsert(RxDocumentServiceRequest request,
                                                   DocumentClientRetryPolicy documentClientRetryPolicy,
                                                   OperationContextAndListenerTuple operationContextAndListenerTuple) {

        return populateHeadersAsync(request, RequestVerb.POST)
            .flatMap(requestPopulated -> {
                Map headers = requestPopulated.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);
                headers.put(HttpConstants.HttpHeaders.IS_UPSERT, "true");
                if (documentClientRetryPolicy.getRetryContext() != null && documentClientRetryPolicy.getRetryContext().getRetryCount() > 0) {
                    documentClientRetryPolicy.getRetryContext().updateEndTime();
                }

                return getStoreProxy(requestPopulated).processMessage(requestPopulated, operationContextAndListenerTuple)
                    .map(response -> {
                            this.captureSessionToken(requestPopulated, response);
                            return response;
                        }
                    );
            });
    }

    private Mono replace(RxDocumentServiceRequest request, DocumentClientRetryPolicy documentClientRetryPolicy) {
        return populateHeadersAsync(request, RequestVerb.PUT)
            .flatMap(requestPopulated -> {
                if (documentClientRetryPolicy.getRetryContext() != null && documentClientRetryPolicy.getRetryContext().getRetryCount() > 0) {
                    documentClientRetryPolicy.getRetryContext().updateEndTime();
                }

                return getStoreProxy(requestPopulated).processMessage(requestPopulated);
            });
    }

    private Mono patch(RxDocumentServiceRequest request, DocumentClientRetryPolicy documentClientRetryPolicy) {
        return populateHeadersAsync(request, RequestVerb.PATCH)
            .flatMap(requestPopulated -> {
                if (documentClientRetryPolicy.getRetryContext() != null && documentClientRetryPolicy.getRetryContext().getRetryCount() > 0) {
                    documentClientRetryPolicy.getRetryContext().updateEndTime();
                }
                return getStoreProxy(requestPopulated).processMessage(requestPopulated);
        });
    }

    @Override
    public Mono> createDocument(
        String collectionLink,
        Object document,
        RequestOptions options,
        boolean disableAutomaticIdGeneration) {

        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Create,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> createDocumentCore(
                collectionLink,
                document,
                opt,
                disableAutomaticIdGeneration,
                e2ecfg,
                clientCtxOverride,
                pointOperationContextForCircuitBreaker
            ),
            options,
            options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled(),
            collectionLink
        );
    }

    private Mono> createDocumentCore(
        String collectionLink,
        Object document,
        RequestOptions options,
        boolean disableAutomaticIdGeneration,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        ScopedDiagnosticsFactory scopedDiagnosticsFactory = new ScopedDiagnosticsFactory(clientContextOverride, false);
        DocumentClientRetryPolicy requestRetryPolicy =
            this.resetSessionTokenRetryPolicy.getRequestPolicy(scopedDiagnosticsFactory);
        RequestOptions nonNullRequestOptions = options != null ? options : new RequestOptions();
        if (nonNullRequestOptions.getPartitionKey() == null) {
            requestRetryPolicy = new PartitionKeyMismatchRetryPolicy(collectionCache, requestRetryPolicy, collectionLink, nonNullRequestOptions);
        }

        DocumentClientRetryPolicy finalRetryPolicyInstance = requestRetryPolicy;
        AtomicReference requestReference = new AtomicReference<>();

        return handleCircuitBreakingFeedbackForPointOperation(getPointOperationResponseMonoWithE2ETimeout(
            nonNullRequestOptions,
            endToEndPolicyConfig,
            ObservableHelper.inlineIfPossibleAsObs(() ->
                    createDocumentInternal(
                        collectionLink,
                        document,
                        nonNullRequestOptions,
                        disableAutomaticIdGeneration,
                        finalRetryPolicyInstance,
                        scopedDiagnosticsFactory,
                        requestReference,
                        pointOperationContextForCircuitBreaker),
                requestRetryPolicy),
            scopedDiagnosticsFactory
        ), requestReference);
    }

    private Mono> createDocumentInternal(
        String collectionLink,
        Object document,
        RequestOptions options,
        boolean disableAutomaticIdGeneration,
        DocumentClientRetryPolicy requestRetryPolicy,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference documentServiceRequestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {
        try {
            logger.debug("Creating a Document. collectionLink: [{}]", collectionLink);

            Mono>> requestToDocumentCollectionObs = getCreateDocumentRequest(
                requestRetryPolicy,
                collectionLink,
                document,
                options,
                disableAutomaticIdGeneration,
                OperationType.Create,
                clientContextOverride,
                pointOperationContextForCircuitBreaker);

            return requestToDocumentCollectionObs
                .flatMap(requestToDocumentCollection -> {

                    RxDocumentServiceRequest request = requestToDocumentCollection.getT1();
                    Utils.ValueHolder documentCollectionValueHolder = requestToDocumentCollection.getT2();

                    if (documentCollectionValueHolder == null || documentCollectionValueHolder.v == null) {
                        return Mono.error(new IllegalStateException("documentCollectionValueHolder or documentCollectionValueHolder.v cannot be null"));
                    }

                    return this.partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), documentCollectionValueHolder.v.getResourceId(), null, null)
                        .flatMap(collectionRoutingMapValueHolder -> {

                            if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) {
                                return Mono.error(new IllegalStateException("collectionRoutingMapValueHolder or collectionRoutingMapValueHolder.v cannot be null"));
                            }

                            options.setPartitionKeyDefinition(documentCollectionValueHolder.v.getPartitionKey());
                            addPartitionLevelUnavailableRegionsForRequest(request, options, collectionRoutingMapValueHolder.v, requestRetryPolicy);
                            documentServiceRequestReference.set(request);
                            request.requestContext.setPointOperationContext(pointOperationContextForCircuitBreaker);

                            // needs to be after onBeforeSendRequest since CosmosDiagnostics instance needs to be wired
                            // to the RxDocumentServiceRequest instance
                            mergeContextInformationIntoDiagnosticsForPointRequest(request, pointOperationContextForCircuitBreaker);

                            return create(request, requestRetryPolicy, getOperationContextAndListenerTuple(options));

                        })
                        .map(serviceResponse -> toResourceResponse(serviceResponse, Document.class));
                });
        } catch (Exception e) {
            logger.debug("Failure in creating a document due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    private static  Mono getPointOperationResponseMonoWithE2ETimeout(
        RequestOptions requestOptions,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        Mono rxDocumentServiceResponseMono,
        ScopedDiagnosticsFactory scopedDiagnosticsFactory) {

        requestOptions.setCosmosEndToEndLatencyPolicyConfig(endToEndPolicyConfig);

        if (endToEndPolicyConfig != null && endToEndPolicyConfig.isEnabled()) {

            Duration endToEndTimeout = endToEndPolicyConfig.getEndToEndOperationTimeout();
            if (endToEndTimeout.isNegative()) {
                CosmosDiagnostics latestCosmosDiagnosticsSnapshot = scopedDiagnosticsFactory.getMostRecentlyCreatedDiagnostics();
                if (latestCosmosDiagnosticsSnapshot == null) {
                    scopedDiagnosticsFactory.createDiagnostics();
                }
                return Mono.error(getNegativeTimeoutException(scopedDiagnosticsFactory.getMostRecentlyCreatedDiagnostics(), endToEndTimeout));
            }

            return rxDocumentServiceResponseMono
                .timeout(endToEndTimeout)
                .onErrorMap(throwable -> getCancellationExceptionForPointOperations(
                    scopedDiagnosticsFactory,
                    throwable,
                    requestOptions.getMarkE2ETimeoutInRequestContextCallbackHook()));
        }
        return rxDocumentServiceResponseMono;
    }

    private  Mono handleCircuitBreakingFeedbackForPointOperation(
        Mono response,
        AtomicReference requestReference) {

        return response
            .doOnSuccess(ignore -> {

                if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(requestReference.get())) {
                    RxDocumentServiceRequest succeededRequest = requestReference.get();
                    checkNotNull(succeededRequest.requestContext, "Argument 'succeededRequest.requestContext' must not be null!");

                    PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker = succeededRequest.requestContext.getPointOperationContextForCircuitBreaker();
                    checkNotNull(pointOperationContextForCircuitBreaker, "Argument 'pointOperationContextForCircuitBreaker' must not be null!");
                    pointOperationContextForCircuitBreaker.setHasOperationSeenSuccess();

                    this.globalPartitionEndpointManagerForCircuitBreaker.handleLocationSuccessForPartitionKeyRange(succeededRequest);
                }
            })
            .doOnError(throwable -> {
                if (throwable instanceof OperationCancelledException) {
                    if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(requestReference.get())) {
                        RxDocumentServiceRequest failedRequest = requestReference.get();
                        checkNotNull(failedRequest.requestContext, "Argument 'failedRequest.requestContext' must not be null!");

                        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker = failedRequest.requestContext.getPointOperationContextForCircuitBreaker();
                        checkNotNull(pointOperationContextForCircuitBreaker, "Argument 'pointOperationContextForCircuitBreaker' must not be null!");

                        if (pointOperationContextForCircuitBreaker.isThresholdBasedAvailabilityStrategyEnabled()) {

                            if (!pointOperationContextForCircuitBreaker.isRequestHedged() && pointOperationContextForCircuitBreaker.getHasOperationSeenSuccess()) {
                                this.handleLocationCancellationExceptionForPartitionKeyRange(failedRequest);
                            }
                        } else {
                            this.handleLocationCancellationExceptionForPartitionKeyRange(failedRequest);
                        }
                    }
                }
            })
            .doFinally(signalType -> {
                if (signalType != SignalType.CANCEL) {
                    return;
                }

                if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(requestReference.get())) {
                    RxDocumentServiceRequest failedRequest = requestReference.get();
                    checkNotNull(failedRequest.requestContext, "Argument 'failedRequest.requestContext' must not be null!");

                    PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker = failedRequest.requestContext.getPointOperationContextForCircuitBreaker();
                    checkNotNull(pointOperationContextForCircuitBreaker, "Argument 'pointOperationContextForCircuitBreaker' must not be null!");

                    // scoping the handling of CANCEL signal handling for reasons outside of end-to-end operation timeout
                    // to purely operations which have end-to-end operation timeout enabled
                    if (pointOperationContextForCircuitBreaker.isThresholdBasedAvailabilityStrategyEnabled()) {

                        if (!pointOperationContextForCircuitBreaker.isRequestHedged() && pointOperationContextForCircuitBreaker.getHasOperationSeenSuccess()) {
                            this.handleLocationCancellationExceptionForPartitionKeyRange(failedRequest);
                        }
                    }
                }
            });
    }

    private  Mono> handleCircuitBreakingFeedbackForFeedOperationWithAvailabilityStrategy(Mono> response, RxDocumentServiceRequest request) {

        return response
            .doOnSuccess(nonTransientFeedOperationResult -> {

                if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(request)) {
                    if (!nonTransientFeedOperationResult.isError()) {
                        checkNotNull(request, "Argument 'request' cannot be null!");
                        checkNotNull(request.requestContext, "Argument 'request.requestContext' cannot be null!");

                        FeedOperationContextForCircuitBreaker feedOperationContextForCircuitBreaker
                            = request.requestContext.getFeedOperationContextForCircuitBreaker();

                        checkNotNull(feedOperationContextForCircuitBreaker, "Argument 'feedOperationContextForCircuitBreaker' cannot be null!");

                        feedOperationContextForCircuitBreaker.addPartitionKeyRangeWithSuccess(request.requestContext.resolvedPartitionKeyRange, request.getResourceId());
                        this.globalPartitionEndpointManagerForCircuitBreaker.handleLocationSuccessForPartitionKeyRange(request);
                    }
                }
            })
            .doFinally(signalType -> {
                if (signalType != SignalType.CANCEL) {
                    return;
                }

                if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(request)) {
                    checkNotNull(request, "Argument 'request' cannot be null!");
                    checkNotNull(request.requestContext, "Argument 'request.requestContext' cannot be null!");

                    FeedOperationContextForCircuitBreaker feedOperationContextForCircuitBreaker
                        = request.requestContext.getFeedOperationContextForCircuitBreaker();

                    checkNotNull(feedOperationContextForCircuitBreaker, "Argument 'feedOperationContextForCircuitBreaker' cannot be null!");

                    if (!feedOperationContextForCircuitBreaker.getIsRequestHedged()
                        && feedOperationContextForCircuitBreaker.isThresholdBasedAvailabilityStrategyEnabled()
                        && feedOperationContextForCircuitBreaker.hasPartitionKeyRangeSeenSuccess(request.requestContext.resolvedPartitionKeyRange, request.getResourceId())) {
                        this.handleLocationCancellationExceptionForPartitionKeyRange(request);
                    }
                }
            });
    }

    private static Throwable getCancellationExceptionForPointOperations(
        ScopedDiagnosticsFactory scopedDiagnosticsFactory,
        Throwable throwable,
        AtomicReference markE2ETimeoutInRequestContextCallbackHook) {
        Throwable unwrappedException = reactor.core.Exceptions.unwrap(throwable);
        if (unwrappedException instanceof TimeoutException) {

            CosmosException exception = new OperationCancelledException();
            exception.setStackTrace(throwable.getStackTrace());

            Runnable actualCallback = markE2ETimeoutInRequestContextCallbackHook.get();
            if (actualCallback != null) {
                logger.trace("Calling actual Mark E2E timeout callback");
                actualCallback.run();
            }

            // For point operations
            // availabilityStrategy sits on top of e2eTimeoutPolicy
            // e2eTimeoutPolicy sits on top of client retry policy
            // for each e2eTimeoutPolicy wrap, we are going to create one distinct ScopedDiagnosticsFactory
            // so for each scopedDiagnosticsFactory being used here, there will only be max one CosmosDiagnostics being tracked
            CosmosDiagnostics lastDiagnosticsSnapshot = scopedDiagnosticsFactory.getMostRecentlyCreatedDiagnostics();
            if (lastDiagnosticsSnapshot == null) {
                scopedDiagnosticsFactory.createDiagnostics();
            }
            BridgeInternal.setCosmosDiagnostics(exception, scopedDiagnosticsFactory.getMostRecentlyCreatedDiagnostics());

            return exception;
        }
        return throwable;
    }

    private static CosmosException getNegativeTimeoutException(CosmosDiagnostics cosmosDiagnostics, Duration negativeTimeout) {
        checkNotNull(negativeTimeout, "Argument 'negativeTimeout' must not be null");
        checkArgument(
            negativeTimeout.isNegative(),
            "This exception should only be used for negative timeouts");

        String message = String.format("Negative timeout '%s' provided.",  negativeTimeout);
        CosmosException exception = new OperationCancelledException(message, null);
        BridgeInternal.setSubStatusCode(exception, HttpConstants.SubStatusCodes.NEGATIVE_TIMEOUT_PROVIDED);

        if (cosmosDiagnostics != null) {
            BridgeInternal.setCosmosDiagnostics(exception, cosmosDiagnostics);
        }

        return exception;
    }

    @Override
    public Mono> upsertDocument(String collectionLink, Object document,
                                                                 RequestOptions options, boolean disableAutomaticIdGeneration) {
        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Upsert,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> upsertDocumentCore(
                collectionLink, document, opt, disableAutomaticIdGeneration, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker),
            options,
            options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled(),
            collectionLink
        );
    }

    private Mono> upsertDocumentCore(
        String collectionLink,
        Object document,
        RequestOptions options,
        boolean disableAutomaticIdGeneration,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        RequestOptions nonNullRequestOptions = options != null ? options : new RequestOptions();
        ScopedDiagnosticsFactory scopedDiagnosticsFactory = new ScopedDiagnosticsFactory(clientContextOverride, false);
        DocumentClientRetryPolicy requestRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(scopedDiagnosticsFactory);
        if (nonNullRequestOptions.getPartitionKey() == null) {
            requestRetryPolicy = new PartitionKeyMismatchRetryPolicy(collectionCache, requestRetryPolicy, collectionLink, nonNullRequestOptions);
        }

        DocumentClientRetryPolicy finalRetryPolicyInstance = requestRetryPolicy;
        AtomicReference requestReference = new AtomicReference<>();

        return handleCircuitBreakingFeedbackForPointOperation(getPointOperationResponseMonoWithE2ETimeout(
                nonNullRequestOptions,
                endToEndPolicyConfig,
                ObservableHelper.inlineIfPossibleAsObs(
                    () -> upsertDocumentInternal(
                        collectionLink,
                        document,
                        nonNullRequestOptions,
                        disableAutomaticIdGeneration,
                        finalRetryPolicyInstance,
                        scopedDiagnosticsFactory,
                        requestReference,
                        pointOperationContextForCircuitBreaker),
                    finalRetryPolicyInstance),
                scopedDiagnosticsFactory), requestReference);
    }

    private Mono> upsertDocumentInternal(
        String collectionLink,
        Object document,
        RequestOptions options,
        boolean disableAutomaticIdGeneration,
        DocumentClientRetryPolicy retryPolicyInstance,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference requestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        try {
            logger.debug("Upserting a Document. collectionLink: [{}]", collectionLink);

            Mono>> requestToDocumentCollectionObs =
                getCreateDocumentRequest(
                    retryPolicyInstance,
                    collectionLink,
                    document,
                    options,
                    disableAutomaticIdGeneration,
                    OperationType.Upsert,
                    clientContextOverride,
                    pointOperationContextForCircuitBreaker);

            return requestToDocumentCollectionObs
                .flatMap(requestToDocumentCollection -> {
                    RxDocumentServiceRequest request = requestToDocumentCollection.getT1();
                    Utils.ValueHolder documentCollectionValueHolder = requestToDocumentCollection.getT2();

                    if (documentCollectionValueHolder == null || documentCollectionValueHolder.v == null) {
                        return Mono.error(new IllegalStateException("documentCollectionValueHolder or documentCollectionValueHolder.v cannot be null"));
                    }

                    return this.partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), documentCollectionValueHolder.v.getResourceId(), null, null)
                        .flatMap(collectionRoutingMapValueHolder -> {

                            if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) {
                                return Mono.error(new IllegalStateException("collectionRoutingMapValueHolder or collectionRoutingMapValueHolder.v cannot be null"));
                            }

                            options.setPartitionKeyDefinition(documentCollectionValueHolder.v.getPartitionKey());
                            addPartitionLevelUnavailableRegionsForRequest(request, options, collectionRoutingMapValueHolder.v, retryPolicyInstance);

                            request.requestContext.setPointOperationContext(pointOperationContextForCircuitBreaker);
                            requestReference.set(request);

                            // needs to be after onBeforeSendRequest since CosmosDiagnostics instance needs to be wired
                            // to the RxDocumentServiceRequest instance
                            mergeContextInformationIntoDiagnosticsForPointRequest(request, pointOperationContextForCircuitBreaker);

                            return upsert(request, retryPolicyInstance, getOperationContextAndListenerTuple(options));
                        })
                        .map(serviceResponse -> toResourceResponse(serviceResponse, Document.class));

                });

        } catch (Exception e) {
            logger.debug("Failure in upserting a document due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> replaceDocument(String documentLink, Object document,
                                                            RequestOptions options) {

        String collectionLink = Utils.getCollectionName(documentLink);

        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Replace,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> replaceDocumentCore(
                documentLink,
                document,
                opt,
                e2ecfg,
                clientCtxOverride,
                pointOperationContextForCircuitBreaker),
            options,
            options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled(),
            collectionLink
        );
    }

    private Mono> replaceDocumentCore(
        String documentLink,
        Object document,
        RequestOptions options,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        RequestOptions nonNullRequestOptions = options != null ? options : new RequestOptions();
        ScopedDiagnosticsFactory scopedDiagnosticsFactory = new ScopedDiagnosticsFactory(clientContextOverride, false);
        DocumentClientRetryPolicy requestRetryPolicy =
            this.resetSessionTokenRetryPolicy.getRequestPolicy(scopedDiagnosticsFactory);
        if (nonNullRequestOptions.getPartitionKey() == null) {
            String collectionLink = Utils.getCollectionName(documentLink);
            requestRetryPolicy = new PartitionKeyMismatchRetryPolicy(
                collectionCache, requestRetryPolicy, collectionLink, nonNullRequestOptions);
        }
        DocumentClientRetryPolicy finalRequestRetryPolicy = requestRetryPolicy;
        AtomicReference requestReference = new AtomicReference<>();

        return handleCircuitBreakingFeedbackForPointOperation(getPointOperationResponseMonoWithE2ETimeout(
                nonNullRequestOptions,
                endToEndPolicyConfig,
                ObservableHelper.inlineIfPossibleAsObs(
                    () -> replaceDocumentInternal(
                        documentLink,
                        document,
                        nonNullRequestOptions,
                        finalRequestRetryPolicy,
                        endToEndPolicyConfig,
                        scopedDiagnosticsFactory,
                        requestReference,
                        pointOperationContextForCircuitBreaker),
                    requestRetryPolicy),
                scopedDiagnosticsFactory), requestReference);
    }

    private Mono> replaceDocumentInternal(
        String documentLink,
        Object document,
        RequestOptions options,
        DocumentClientRetryPolicy retryPolicyInstance,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference requestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        try {
            if (StringUtils.isEmpty(documentLink)) {
                throw new IllegalArgumentException("documentLink");
            }

            if (document == null) {
                throw new IllegalArgumentException("document");
            }

            Document typedDocument = Document.fromObject(document, options.getEffectiveItemSerializer());

            return this.replaceDocumentInternal(
                documentLink,
                typedDocument,
                options,
                retryPolicyInstance,
                clientContextOverride,
                requestReference,
                pointOperationContextForCircuitBreaker);

        } catch (Exception e) {
            logger.debug("Failure in replacing a document due to [{}]", e.getMessage());
            return Mono.error(e);
        }
    }

    @Override
    public Mono> replaceDocument(Document document, RequestOptions options) {

        String collectionLink = Utils.getCollectionName(document.getSelfLink());

        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Replace,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> replaceDocumentCore(
                document,
                opt,
                e2ecfg,
                clientCtxOverride,
                pointOperationContextForCircuitBreaker
            ),
            options,
            options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled(),
            collectionLink
        );
    }

    private Mono> replaceDocumentCore(
        Document document,
        RequestOptions options,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        DocumentClientRetryPolicy requestRetryPolicy =
            this.resetSessionTokenRetryPolicy.getRequestPolicy(clientContextOverride);
        if (options == null || options.getPartitionKey() == null) {
            String collectionLink = document.getSelfLink();
            requestRetryPolicy = new PartitionKeyMismatchRetryPolicy(
                collectionCache, requestRetryPolicy, collectionLink, options);
        }
        DocumentClientRetryPolicy finalRequestRetryPolicy = requestRetryPolicy;
        AtomicReference requestReference = new AtomicReference<>();

        return handleCircuitBreakingFeedbackForPointOperation(ObservableHelper.inlineIfPossibleAsObs(
            () -> replaceDocumentInternal(
                document,
                options,
                finalRequestRetryPolicy,
                endToEndPolicyConfig,
                clientContextOverride,
                requestReference,
                pointOperationContextForCircuitBreaker),
            requestRetryPolicy), requestReference);
    }

    private Mono> replaceDocumentInternal(
        Document document,
        RequestOptions options,
        DocumentClientRetryPolicy retryPolicyInstance,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference requestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        try {
            if (document == null) {
                throw new IllegalArgumentException("document");
            }

            return this.replaceDocumentInternal(
                document.getSelfLink(),
                document,
                options,
                retryPolicyInstance,
                clientContextOverride,
                requestReference,
                pointOperationContextForCircuitBreaker);

        } catch (Exception e) {
            logger.debug("Failure in replacing a database due to [{}]", e.getMessage());
            return Mono.error(e);
        }
    }

    private Mono> replaceDocumentInternal(
        String documentLink,
        Document document,
        RequestOptions options,
        DocumentClientRetryPolicy retryPolicyInstance,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference requestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        if (document == null) {
            throw new IllegalArgumentException("document");
        }

        logger.debug("Replacing a Document. documentLink: [{}]", documentLink);
        final String path = Utils.joinPath(documentLink, null);
        final Map requestHeaders =
            getRequestHeaders(options, ResourceType.Document, OperationType.Replace);
        Instant serializationStartTimeUTC = Instant.now();
        Consumer> onAfterSerialization = null;
        if (options != null) {
            String trackingId = options.getTrackingId();

            if (trackingId != null && !trackingId.isEmpty()) {
                onAfterSerialization = (node) -> node.put(Constants.Properties.TRACKING_ID, trackingId);
            }
        }

        ByteBuffer content = document.serializeJsonToByteBuffer(options.getEffectiveItemSerializer(), onAfterSerialization, false);
        Instant serializationEndTime = Instant.now();
        SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics =
            new SerializationDiagnosticsContext.SerializationDiagnostics(
                serializationStartTimeUTC,
                serializationEndTime,
                SerializationDiagnosticsContext.SerializationType.ITEM_SERIALIZATION);

        final RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
            getEffectiveClientContext(clientContextOverride),
            OperationType.Replace, ResourceType.Document, path, requestHeaders, options, content);

        if (options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled()) {
            request.setNonIdempotentWriteRetriesEnabled(true);
        }

        if (options != null) {
            options.getMarkE2ETimeoutInRequestContextCallbackHook().set(
                () -> request.requestContext.setIsRequestCancelledOnTimeout(new AtomicBoolean(true)));
            request.requestContext.setExcludeRegions(options.getExcludedRegions());
            request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());
        }

        SerializationDiagnosticsContext serializationDiagnosticsContext =
            BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);

        if (serializationDiagnosticsContext != null) {
            serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
        }

        if (retryPolicyInstance != null) {
            retryPolicyInstance.onBeforeSendRequest(request);
        }

        Mono> collectionObs =
            collectionCache.resolveCollectionAsync(
                BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics),
                request);
        Mono requestObs =
            addPartitionKeyInformation(request, content, document, options, collectionObs, pointOperationContextForCircuitBreaker);

        return collectionObs
            .flatMap(documentCollectionValueHolder -> {

                if (documentCollectionValueHolder == null || documentCollectionValueHolder.v == null) {
                    return Mono.error(new IllegalStateException("documentCollectionValueHolder or documentCollectionValueHolder.v cannot be null"));
                }

                return this.partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), documentCollectionValueHolder.v.getResourceId(), null, null)
                    .flatMap(collectionRoutingMapValueHolder -> {

                        if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) {
                            return Mono.error(new IllegalStateException("collectionRoutingMapValueHolder or collectionRoutingMapValueHolder.v cannot be null"));
                        }

                        return requestObs.flatMap(req -> {

                                options.setPartitionKeyDefinition(documentCollectionValueHolder.v.getPartitionKey());
                                addPartitionLevelUnavailableRegionsForRequest(req, options, collectionRoutingMapValueHolder.v, retryPolicyInstance);

                                req.requestContext.setPointOperationContext(pointOperationContextForCircuitBreaker);
                                requestReference.set(req);

                                // needs to be after onBeforeSendRequest since CosmosDiagnostics instance needs to be wired
                                // to the RxDocumentServiceRequest instance
                                mergeContextInformationIntoDiagnosticsForPointRequest(request, pointOperationContextForCircuitBreaker);

                                return replace(request, retryPolicyInstance);
                            })
                            .map(resp -> toResourceResponse(resp, Document.class));
                    });
            });
    }

    private CosmosEndToEndOperationLatencyPolicyConfig getEndToEndOperationLatencyPolicyConfig(
        RequestOptions options,
        ResourceType resourceType,
        OperationType operationType) {
        return this.getEffectiveEndToEndOperationLatencyPolicyConfig(
            options != null ? options.getCosmosEndToEndLatencyPolicyConfig() : null,
            resourceType,
            operationType);
    }

    private CosmosEndToEndOperationLatencyPolicyConfig getEffectiveEndToEndOperationLatencyPolicyConfig(
        CosmosEndToEndOperationLatencyPolicyConfig policyConfig,
        ResourceType resourceType,
        OperationType operationType) {
        if (policyConfig != null) {
            return policyConfig;
        }

        if (resourceType != ResourceType.Document) {
            return null;
        }

        if (!operationType.isPointOperation() && Configs.isDefaultE2ETimeoutDisabledForNonPointOperations()) {
            return null;
        }

        return this.cosmosEndToEndOperationLatencyPolicyConfig;
    }

    @Override
    public Mono> patchDocument(String documentLink,
                                                          CosmosPatchOperations cosmosPatchOperations,
                                                          RequestOptions options) {

        String collectionLink = Utils.getCollectionName(documentLink);

        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Patch,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> patchDocumentCore(
                documentLink,
                cosmosPatchOperations,
                opt,
                e2ecfg,
                clientCtxOverride,
                pointOperationContextForCircuitBreaker),
            options,
            options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled(),
            collectionLink
        );
    }

    private Mono> patchDocumentCore(
        String documentLink,
        CosmosPatchOperations cosmosPatchOperations,
        RequestOptions options,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        RequestOptions nonNullRequestOptions = options != null ? options : new RequestOptions();
        ScopedDiagnosticsFactory scopedDiagnosticsFactory = new ScopedDiagnosticsFactory(clientContextOverride, false);
        DocumentClientRetryPolicy documentClientRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(scopedDiagnosticsFactory);

        AtomicReference requestReference = new AtomicReference<>();

        return handleCircuitBreakingFeedbackForPointOperation(
            getPointOperationResponseMonoWithE2ETimeout(
                nonNullRequestOptions,
                endToEndPolicyConfig,
                ObservableHelper.inlineIfPossibleAsObs(
                    () -> patchDocumentInternal(
                        documentLink,
                        cosmosPatchOperations,
                        nonNullRequestOptions,
                        documentClientRetryPolicy,
                        scopedDiagnosticsFactory,
                        requestReference,
                        pointOperationContextForCircuitBreaker),
                    documentClientRetryPolicy),
                scopedDiagnosticsFactory), requestReference);
    }

    private Mono> patchDocumentInternal(
        String documentLink,
        CosmosPatchOperations cosmosPatchOperations,
        RequestOptions options,
        DocumentClientRetryPolicy retryPolicyInstance,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference requestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        checkArgument(StringUtils.isNotEmpty(documentLink), "expected non empty documentLink");
        checkNotNull(cosmosPatchOperations, "expected non null cosmosPatchOperations");

        logger.debug("Running patch operations on Document. documentLink: [{}]", documentLink);

        final String path = Utils.joinPath(documentLink, null);

        final Map requestHeaders =
            getRequestHeaders(options, ResourceType.Document, OperationType.Patch);
        Instant serializationStartTimeUTC = Instant.now();

        ByteBuffer content = ByteBuffer.wrap(
            PatchUtil.serializeCosmosPatchToByteArray(cosmosPatchOperations, options));

        Instant serializationEndTime = Instant.now();
        SerializationDiagnosticsContext.SerializationDiagnostics serializationDiagnostics =
            new SerializationDiagnosticsContext.SerializationDiagnostics(
                serializationStartTimeUTC,
                serializationEndTime,
                SerializationDiagnosticsContext.SerializationType.ITEM_SERIALIZATION);

        final RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
            clientContextOverride,
            OperationType.Patch,
            ResourceType.Document,
            path,
            requestHeaders,
            options,
            content);

        if (options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled()) {
            request.setNonIdempotentWriteRetriesEnabled(true);
        }
        if (options != null) {
            options.getMarkE2ETimeoutInRequestContextCallbackHook().set(
                () -> request.requestContext.setIsRequestCancelledOnTimeout(new AtomicBoolean(true)));
            request.requestContext.setExcludeRegions(options.getExcludedRegions());
            request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());
        }

        if (retryPolicyInstance != null) {
            retryPolicyInstance.onBeforeSendRequest(request);
        }

        SerializationDiagnosticsContext serializationDiagnosticsContext =
            BridgeInternal.getSerializationDiagnosticsContext(request.requestContext.cosmosDiagnostics);

        if (serializationDiagnosticsContext != null) {
            serializationDiagnosticsContext.addSerializationDiagnostics(serializationDiagnostics);
        }

        Mono> collectionObs = collectionCache.resolveCollectionAsync(
            BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request);

        // options will always have partition key info, so contentAsByteBuffer can be null and is not needed.
        Mono requestObs = addPartitionKeyInformation(
            request,
            null,
            null,
            options,
            collectionObs,
            pointOperationContextForCircuitBreaker);

        return collectionObs
            .flatMap(documentCollectionValueHolder -> {

                if (documentCollectionValueHolder == null || documentCollectionValueHolder.v == null) {
                    return Mono.error(new IllegalStateException("documentCollectionValueHolder or documentCollectionValueHolder.v cannot be null"));
                }

                return this.partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), documentCollectionValueHolder.v.getResourceId(), null, null)
                    .flatMap(collectionRoutingMapValueHolder -> {

                        if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) {
                            return Mono.error(new IllegalStateException("collectionRoutingMapValueHolder or collectionRoutingMapValueHolder.v cannot be null"));
                        }

                        return requestObs
                            .flatMap(req -> {

                                options.setPartitionKeyDefinition(documentCollectionValueHolder.v.getPartitionKey());
                                addPartitionLevelUnavailableRegionsForRequest(req, options, collectionRoutingMapValueHolder.v, retryPolicyInstance);

                                req.requestContext.setPointOperationContext(pointOperationContextForCircuitBreaker);
                                requestReference.set(req);

                                // needs to be after onBeforeSendRequest since CosmosDiagnostics instance needs to be wired
                                // to the RxDocumentServiceRequest instance
                                mergeContextInformationIntoDiagnosticsForPointRequest(request, pointOperationContextForCircuitBreaker);

                                return patch(request, retryPolicyInstance);
                            })
                            .map(resp -> toResourceResponse(resp, Document.class));
                    });
            });
    }

    @Override
    public Mono> deleteDocument(String documentLink, RequestOptions options) {

        String collectionLink = Utils.getCollectionName(documentLink);

        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Delete,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> deleteDocumentCore(
                documentLink,
                null,
                opt,
                e2ecfg,
                clientCtxOverride,
                pointOperationContextForCircuitBreaker
            ),
            options,
            options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled(),
            collectionLink
        );
    }

    @Override
    public Mono> deleteDocument(String documentLink, InternalObjectNode internalObjectNode, RequestOptions options) {

        String collectionLink = Utils.getCollectionName(documentLink);

        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Delete,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> deleteDocumentCore(
                documentLink,
                internalObjectNode,
                opt,
                e2ecfg,
                clientCtxOverride,
                pointOperationContextForCircuitBreaker),
            options,
            options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled(),
            collectionLink
        );
    }

    private Mono> deleteDocumentCore(
        String documentLink,
        InternalObjectNode internalObjectNode,
        RequestOptions options,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        RequestOptions nonNullRequestOptions = options != null ? options : new RequestOptions();
        ScopedDiagnosticsFactory scopedDiagnosticsFactory = new ScopedDiagnosticsFactory(clientContextOverride, false);
        DocumentClientRetryPolicy requestRetryPolicy =
            this.resetSessionTokenRetryPolicy.getRequestPolicy(scopedDiagnosticsFactory);

        AtomicReference requestReference = new AtomicReference<>();

        return handleCircuitBreakingFeedbackForPointOperation(getPointOperationResponseMonoWithE2ETimeout(
                nonNullRequestOptions,
                endToEndPolicyConfig,
                ObservableHelper.inlineIfPossibleAsObs(
                    () -> deleteDocumentInternal(
                        documentLink,
                        internalObjectNode,
                        nonNullRequestOptions,
                        requestRetryPolicy,
                        scopedDiagnosticsFactory,
                        requestReference,
                        pointOperationContextForCircuitBreaker),
                    requestRetryPolicy),
                scopedDiagnosticsFactory), requestReference);
    }

    private Mono> deleteDocumentInternal(
        String documentLink,
        InternalObjectNode internalObjectNode,
        RequestOptions options,
        DocumentClientRetryPolicy retryPolicyInstance,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference requestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        try {
            if (StringUtils.isEmpty(documentLink)) {
                throw new IllegalArgumentException("documentLink");
            }

            logger.debug("Deleting a Document. documentLink: [{}]", documentLink);
            String path = Utils.joinPath(documentLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.Document, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
                getEffectiveClientContext(clientContextOverride),
                OperationType.Delete, ResourceType.Document, path, requestHeaders, options);

            if (options != null && options.getNonIdempotentWriteRetriesEnabled() != null && options.getNonIdempotentWriteRetriesEnabled()) {
                request.setNonIdempotentWriteRetriesEnabled(true);
            }

            if (options != null) {
                options.getMarkE2ETimeoutInRequestContextCallbackHook().set(
                    () -> request.requestContext.setIsRequestCancelledOnTimeout(new AtomicBoolean(true)));
                request.requestContext.setExcludeRegions(options.getExcludedRegions());
                request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());
            }

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            Mono> collectionObs = collectionCache.resolveCollectionAsync(
                BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics),
                request);

            Mono requestObs = addPartitionKeyInformation(
                request, null, internalObjectNode, options, collectionObs, pointOperationContextForCircuitBreaker);

            return collectionObs
                .flatMap(documentCollectionValueHolder -> this.partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), documentCollectionValueHolder.v.getResourceId(), null, null)
                    .flatMap(collectionRoutingMapValueHolder -> {
                        return requestObs
                            .flatMap(req -> {

                                options.setPartitionKeyDefinition(documentCollectionValueHolder.v.getPartitionKey());
                                addPartitionLevelUnavailableRegionsForRequest(request, options, collectionRoutingMapValueHolder.v, retryPolicyInstance);

                                req.requestContext.setPointOperationContext(pointOperationContextForCircuitBreaker);
                                requestReference.set(req);

                                // needs to be after onBeforeSendRequest since CosmosDiagnostics instance needs to be wired
                                // to the RxDocumentServiceRequest instance
                                mergeContextInformationIntoDiagnosticsForPointRequest(request, pointOperationContextForCircuitBreaker);

                                return this.delete(req, retryPolicyInstance, getOperationContextAndListenerTuple(options));
                            })
                            .map(serviceResponse -> toResourceResponse(serviceResponse, Document.class));

                    }));
        } catch (Exception e) {
            logger.debug("Failure in deleting a document due to [{}]", e.getMessage());
            return Mono.error(e);
        }
    }

    @Override
    public Mono> deleteAllDocumentsByPartitionKey(String collectionLink, PartitionKey partitionKey, RequestOptions options) {
        // No ned-to-end policy / availability strategy applicable because PK Delete is a Gateway/Control-Plane operation
        DocumentClientRetryPolicy requestRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteAllDocumentsByPartitionKeyInternal(collectionLink, options, requestRetryPolicy),
            requestRetryPolicy);
    }

    private Mono> deleteAllDocumentsByPartitionKeyInternal(String collectionLink, RequestOptions options,
                                                                                  DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (StringUtils.isEmpty(collectionLink)) {
                throw new IllegalArgumentException("collectionLink");
            }

            logger.debug("Deleting all items by Partition Key. collectionLink: [{}]", collectionLink);
            String path = Utils.joinPath(collectionLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.PartitionKey, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.PartitionKey, path, requestHeaders, options);
            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            Mono> collectionObs = collectionCache.resolveCollectionAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request);

            Mono requestObs = addPartitionKeyInformation(request, null, null, options, collectionObs, null);

            return requestObs.flatMap(req -> this
                .deleteAllItemsByPartitionKey(req, retryPolicyInstance, getOperationContextAndListenerTuple(options))
                .map(serviceResponse -> toResourceResponse(serviceResponse, Document.class)));
        } catch (Exception e) {
            logger.debug("Failure in deleting documents due to [{}]", e.getMessage());
            return Mono.error(e);
        }
    }

    @Override
    public Mono> readDocument(String documentLink, RequestOptions options) {
        return readDocument(documentLink, options, this);
    }

    private Mono> readDocument(
        String documentLink,
        RequestOptions options,
        DiagnosticsClientContext innerDiagnosticsFactory) {

        String collectionLink = Utils.getCollectionName(documentLink);

        return wrapPointOperationWithAvailabilityStrategy(
            ResourceType.Document,
            OperationType.Read,
            (opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker) -> readDocumentCore(documentLink, opt, e2ecfg, clientCtxOverride, pointOperationContextForCircuitBreaker),
            options,
            false,
            innerDiagnosticsFactory,
            collectionLink
        );
    }

    private Mono> readDocumentCore(
        String documentLink,
        RequestOptions options,
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig,
        DiagnosticsClientContext clientContextOverride,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        RequestOptions nonNullRequestOptions = options != null ? options : new RequestOptions();
        ScopedDiagnosticsFactory scopedDiagnosticsFactory = new ScopedDiagnosticsFactory(clientContextOverride, false);
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(scopedDiagnosticsFactory);

        AtomicReference requestReference = new AtomicReference<>();

        return handleCircuitBreakingFeedbackForPointOperation(getPointOperationResponseMonoWithE2ETimeout(
            nonNullRequestOptions,
            endToEndPolicyConfig,
            ObservableHelper.inlineIfPossibleAsObs(
                () -> readDocumentInternal(
                    documentLink,
                    nonNullRequestOptions,
                    retryPolicyInstance,
                    scopedDiagnosticsFactory,
                    requestReference,
                    pointOperationContextForCircuitBreaker),
                retryPolicyInstance),
            scopedDiagnosticsFactory
        ), requestReference);
    }

    private Mono> readDocumentInternal(
        String documentLink,
        RequestOptions options,
        DocumentClientRetryPolicy retryPolicyInstance,
        DiagnosticsClientContext clientContextOverride,
        AtomicReference requestReference,
        PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) {

        try {
            if (StringUtils.isEmpty(documentLink)) {
                throw new IllegalArgumentException("documentLink");
            }

            logger.debug("Reading a Document. documentLink: [{}]", documentLink);
            String path = Utils.joinPath(documentLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.Document, OperationType.Read);

            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
                getEffectiveClientContext(clientContextOverride),
                OperationType.Read, ResourceType.Document, path, requestHeaders, options);
            options.getMarkE2ETimeoutInRequestContextCallbackHook().set(
                () -> request.requestContext.setIsRequestCancelledOnTimeout(new AtomicBoolean(true)));
            request.requestContext.setExcludeRegions(options.getExcludedRegions());
            request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            Mono> collectionObs = this.collectionCache.resolveCollectionAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request);
            return collectionObs.flatMap(documentCollectionValueHolder -> {

                    if (documentCollectionValueHolder == null || documentCollectionValueHolder.v == null) {
                        return Mono.error(new IllegalStateException("documentCollectionValueHolder or documentCollectionValueHolder.v cannot be null"));
                    }

                    DocumentCollection documentCollection = documentCollectionValueHolder.v;
                    return this.partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), documentCollection.getResourceId(), null, null)
                        .flatMap(collectionRoutingMapValueHolder -> {

                            if (collectionRoutingMapValueHolder == null || collectionRoutingMapValueHolder.v == null) {
                                return Mono.error(new IllegalStateException("collectionRoutingMapValueHolder or collectionRoutingMapValueHolder.v cannot be null"));
                            }

                            Mono requestObs = addPartitionKeyInformation(request, null, null, options, collectionObs, pointOperationContextForCircuitBreaker);

                            return requestObs.flatMap(req -> {

                                options.setPartitionKeyDefinition(documentCollection.getPartitionKey());
                                addPartitionLevelUnavailableRegionsForRequest(req, options, collectionRoutingMapValueHolder.v, retryPolicyInstance);

                                req.requestContext.setPointOperationContext(pointOperationContextForCircuitBreaker);
                                requestReference.set(req);

                                // needs to be after onBeforeSendRequest since CosmosDiagnostics instance needs to be wired
                                // to the RxDocumentServiceRequest instance
                                mergeContextInformationIntoDiagnosticsForPointRequest(request, pointOperationContextForCircuitBreaker);

                                return this.read(req, retryPolicyInstance)
                                    .map(serviceResponse -> toResourceResponse(serviceResponse, Document.class));
                            });

                        });

                }
            );
        } catch (Exception e) {
            logger.debug("Failure in reading a document due to [{}]", e.getMessage());
            return Mono.error(e);
        }
    }

    @Override
    public  Flux>  readDocuments(
        String collectionLink, QueryFeedOperationState state, Class classOfT) {

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        return queryDocuments(collectionLink, "SELECT * FROM r", state, classOfT);
    }

    @Override
    public  Mono> readMany(
        List itemIdentityList,
        String collectionLink,
        QueryFeedOperationState state,
        Class klass) {

        final ScopedDiagnosticsFactory diagnosticsFactory = new ScopedDiagnosticsFactory(this, true);
        state.registerDiagnosticsFactory(
            () -> {}, // we never want to reset in readMany
            (ctx) -> diagnosticsFactory.merge(ctx)
        );

        String resourceLink = parentResourceLinkToQueryLink(collectionLink, ResourceType.Document);
        RxDocumentServiceRequest request = RxDocumentServiceRequest.create(diagnosticsFactory,
            OperationType.Query,
            ResourceType.Document,
            collectionLink, null
        );

        // This should not get to backend
        Mono> collectionObs =
            collectionCache.resolveCollectionAsync(null, request);

        return collectionObs
            .flatMap(documentCollectionResourceResponse -> {
                    final DocumentCollection collection = documentCollectionResourceResponse.v;
                    if (collection == null) {
                        return Mono.error(new IllegalStateException("Collection cannot be null"));
                    }

                    final PartitionKeyDefinition pkDefinition = collection.getPartitionKey();

                    Mono> valueHolderMono = partitionKeyRangeCache
                        .tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics),
                            collection.getResourceId(),
                            null,
                            null);

                    return valueHolderMono
                        .flatMap(collectionRoutingMapValueHolder -> {
                            Map> partitionRangeItemKeyMap = new HashMap<>();
                            CollectionRoutingMap routingMap = collectionRoutingMapValueHolder.v;
                            if (routingMap == null) {
                                return Mono.error(new IllegalStateException("Failed to get routing map."));
                            }
                            itemIdentityList
                                .forEach(itemIdentity -> {
                                    //Check no partial partition keys are being used
                                    if (pkDefinition.getKind().equals(PartitionKind.MULTI_HASH) &&
                                        ModelBridgeInternal.getPartitionKeyInternal(itemIdentity.getPartitionKey())
                                                           .getComponents().size() != pkDefinition.getPaths().size()) {
                                        throw new IllegalArgumentException(RMResources.PartitionKeyMismatch);
                                    }
                                    String effectivePartitionKeyString = PartitionKeyInternalHelper
                                        .getEffectivePartitionKeyString(
                                            BridgeInternal.getPartitionKeyInternal(
                                                itemIdentity.getPartitionKey()),
                                            pkDefinition);

                                    //use routing map to find the partitionKeyRangeId of each
                                    // effectivePartitionKey
                                    PartitionKeyRange range =
                                        routingMap.getRangeByEffectivePartitionKey(effectivePartitionKeyString);

                                    //group the itemKeyList based on partitionKeyRangeId
                                    if (partitionRangeItemKeyMap.get(range) == null) {
                                        List list = new ArrayList<>();
                                        list.add(itemIdentity);
                                        partitionRangeItemKeyMap.put(range, list);
                                    } else {
                                        List pairs =
                                            partitionRangeItemKeyMap.get(range);
                                        pairs.add(itemIdentity);
                                        partitionRangeItemKeyMap.put(range, pairs);
                                    }

                                });

                            //Create the range query map that contains the query to be run for that
                            // partitionkeyrange
                            Map rangeQueryMap = getRangeQueryMap(partitionRangeItemKeyMap, collection.getPartitionKey());

                            // create point reads
                            Flux> pointReads = pointReadsForReadMany(
                                diagnosticsFactory,
                                partitionRangeItemKeyMap,
                                resourceLink,
                                state.getQueryOptions(),
                                klass);

                            // create the executable query
                            Flux> queries = queryForReadMany(
                                diagnosticsFactory,
                                resourceLink,
                                new SqlQuerySpec(DUMMY_SQL_QUERY),
                                state.getQueryOptions(),
                                klass,
                                ResourceType.Document,
                                collection,
                                Collections.unmodifiableMap(rangeQueryMap));

                            // merge results from point reads and queries
                            return Flux.merge(pointReads, queries)
                                       .collectList()
                                       // aggregating the result to construct a FeedResponse and aggregate RUs.
                                       .map(feedList -> {
                                           List finalList = new ArrayList<>();
                                           HashMap headers = new HashMap<>();
                                           ConcurrentMap aggregatedQueryMetrics = new ConcurrentHashMap<>();
                                           Collection aggregateRequestStatistics = new DistinctClientSideRequestStatisticsCollection();
                                           double requestCharge = 0;
                                           for (FeedResponse page : feedList) {
                                               ConcurrentMap pageQueryMetrics =
                                                   ModelBridgeInternal.queryMetrics(page);
                                               if (pageQueryMetrics != null) {
                                                   pageQueryMetrics.forEach(
                                                       aggregatedQueryMetrics::putIfAbsent);
                                               }

                                               requestCharge += page.getRequestCharge();
                                               finalList.addAll(page.getResults());
                                               aggregateRequestStatistics.addAll(diagnosticsAccessor.getClientSideRequestStatistics(page.getCosmosDiagnostics()));
                                           }

                                           // NOTE: This CosmosDiagnostics instance intentionally isn't captured in the
                                           // ScopedDiagnosticsFactory - and a ssuch won't be included in the diagnostics of the
                                           // CosmosDiagnosticsContext - which is fine, because the CosmosDiagnosticsContext
                                           // contains the "real" CosmosDiagnostics instances (which will also be used
                                           // for diagnostics purposes - like metrics, logging etc.
                                           // this artificial CosmosDiagnostics with the aggregated RU/s etc. si simply
                                           // to maintain the API contract that a FeedResponse returns one CosmosDiagnostics
                                           CosmosDiagnostics aggregatedDiagnostics = BridgeInternal.createCosmosDiagnostics(aggregatedQueryMetrics);
                                           diagnosticsAccessor.addClientSideDiagnosticsToFeed(
                                               aggregatedDiagnostics, aggregateRequestStatistics);

                                           state.mergeDiagnosticsContext();
                                           CosmosDiagnosticsContext ctx = state.getDiagnosticsContextSnapshot();
                                           if (ctx != null) {
                                               ctxAccessor.recordOperation(
                                                   ctx,
                                                   200,
                                                   0,
                                                   finalList.size(),
                                                   requestCharge,
                                                   aggregatedDiagnostics,
                                                   null
                                               );
                                               diagnosticsAccessor
                                                   .setDiagnosticsContext(
                                                       aggregatedDiagnostics,
                                                       ctx);
                                           }

                                           headers.put(HttpConstants.HttpHeaders.REQUEST_CHARGE, Double
                                               .toString(requestCharge));
                                           FeedResponse frp = BridgeInternal
                                               .createFeedResponseWithQueryMetrics(
                                                   finalList,
                                                   headers,
                                                   aggregatedQueryMetrics,
                                                   null,
                                                   false,
                                                   false,
                                                   aggregatedDiagnostics);
                                           return frp;
                                       });
                        })
                        .onErrorMap(throwable -> {
                            if (throwable instanceof CosmosException) {
                                CosmosException cosmosException = (CosmosException)throwable;
                                CosmosDiagnostics diagnostics = cosmosException.getDiagnostics();
                                if (diagnostics != null) {
                                    state.mergeDiagnosticsContext();
                                    CosmosDiagnosticsContext ctx = state.getDiagnosticsContextSnapshot();
                                    if (ctx != null) {
                                        ctxAccessor.recordOperation(
                                            ctx,
                                            cosmosException.getStatusCode(),
                                            cosmosException.getSubStatusCode(),
                                            0,
                                            cosmosException.getRequestCharge(),
                                            diagnostics,
                                            throwable
                                        );
                                        diagnosticsAccessor
                                            .setDiagnosticsContext(
                                                diagnostics,
                                                state.getDiagnosticsContextSnapshot());
                                    }
                                }

                                return cosmosException;
                            }

                            return throwable;
                        });
                }
            );
    }

    private Map getRangeQueryMap(
        Map> partitionRangeItemKeyMap,
        PartitionKeyDefinition partitionKeyDefinition) {
        //TODO: Optimise this to include all types of partitionkeydefinitions. ex: c["prop1./ab"]["key1"]

        Map rangeQueryMap = new HashMap<>();
        List partitionKeySelectors = createPkSelectors(partitionKeyDefinition);

        for(Map.Entry> entry: partitionRangeItemKeyMap.entrySet()) {
            SqlQuerySpec sqlQuerySpec;
            List cosmosItemIdentityList = entry.getValue();
            if (cosmosItemIdentityList.size() > 1) {
                if (partitionKeySelectors.size() == 1 && partitionKeySelectors.get(0).equals("[\"id\"]")) {
                    sqlQuerySpec = createReadManyQuerySpecPartitionKeyIdSame(cosmosItemIdentityList);
                } else {
                    sqlQuerySpec = createReadManyQuerySpec(entry.getValue(), partitionKeySelectors);
                }
                // Add query for this partition to rangeQueryMap
                rangeQueryMap.put(entry.getKey(), sqlQuerySpec);
            }
        }

        return rangeQueryMap;
    }

    private SqlQuerySpec createReadManyQuerySpecPartitionKeyIdSame(List idPartitionKeyPairList) {

        StringBuilder queryStringBuilder = new StringBuilder();
        List parameters = new ArrayList<>();

        queryStringBuilder.append("SELECT * FROM c WHERE c.id IN ( ");
        for (int i = 0; i < idPartitionKeyPairList.size(); i++) {
            CosmosItemIdentity itemIdentity = idPartitionKeyPairList.get(i);

            String idValue = itemIdentity.getId();
            String idParamName = "@param" + i;

            parameters.add(new SqlParameter(idParamName, idValue));
            queryStringBuilder.append(idParamName);

            if (i < idPartitionKeyPairList.size() - 1) {
                queryStringBuilder.append(", ");
            }
        }
        queryStringBuilder.append(" )");

        return new SqlQuerySpec(queryStringBuilder.toString(), parameters);
    }

    private SqlQuerySpec createReadManyQuerySpec(
        List itemIdentities,
        List partitionKeySelectors) {
        StringBuilder queryStringBuilder = new StringBuilder();
        List parameters = new ArrayList<>();

        queryStringBuilder.append("SELECT * FROM c WHERE ( ");
        int paramCount = 0;
        for (int i = 0; i < itemIdentities.size(); i++) {
            CosmosItemIdentity itemIdentity = itemIdentities.get(i);

            PartitionKey pkValueAsPartitionKey = itemIdentity.getPartitionKey();
            Object[] pkValues = ModelBridgeInternal.getPartitionKeyInternal(pkValueAsPartitionKey).toObjectArray();
            List> partitionKeyParams = new ArrayList<>();
            int pathCount = 0;
            for (Object pkComponentValue : pkValues) {
                String pkParamName = "@param" + paramCount;
                partitionKeyParams.add(Arrays.asList(partitionKeySelectors.get(pathCount), pkParamName));
                parameters.add(new SqlParameter(pkParamName, pkComponentValue));
                paramCount++;
                pathCount++;
            }

            String idValue = itemIdentity.getId();
            String idParamName = "@param" + paramCount;
            paramCount++;
            parameters.add(new SqlParameter(idParamName, idValue));

            queryStringBuilder.append("(");
            queryStringBuilder.append("c.id = ");
            queryStringBuilder.append(idParamName);

            // partition key def
            for (List pkParam: partitionKeyParams) {
                queryStringBuilder.append(" AND ");
                queryStringBuilder.append(" c");
                queryStringBuilder.append(pkParam.get(0));
                queryStringBuilder.append((" = "));
                queryStringBuilder.append(pkParam.get(1));
            }
            queryStringBuilder.append(" )");

            if (i < itemIdentities.size() - 1) {
                queryStringBuilder.append(" OR ");
            }
        }
        queryStringBuilder.append(" )");

        return new SqlQuerySpec(queryStringBuilder.toString(), parameters);
    }

    private List createPkSelectors(PartitionKeyDefinition partitionKeyDefinition) {
        return partitionKeyDefinition.getPaths()
            .stream()
            .map(pathPart -> StringUtils.substring(pathPart, 1)) // skip starting /
            .map(pathPart -> StringUtils.replace(pathPart, "\"", "\\")) // escape quote
            .map(part -> "[\"" + part + "\"]")
            .collect(Collectors.toList());
    }

    private  Flux> queryForReadMany(
        ScopedDiagnosticsFactory diagnosticsFactory,
        String parentResourceLink,
        SqlQuerySpec sqlQuery,
        CosmosQueryRequestOptions options,
        Class klass,
        ResourceType resourceTypeEnum,
        DocumentCollection collection,
        Map rangeQueryMap) {

        if (rangeQueryMap.isEmpty()) {
            return Flux.empty();
        }

        UUID activityId = randomUuid();

        final AtomicBoolean isQueryCancelledOnTimeout = new AtomicBoolean(false);

        IDocumentQueryClient queryClient = documentQueryClientImpl(RxDocumentClientImpl.this, getOperationContextAndListenerTuple(options));
        Flux> executionContext =
            DocumentQueryExecutionContextFactory.createReadManyQueryAsync(
                diagnosticsFactory,
                queryClient,
                collection.getResourceId(),
                sqlQuery,
                rangeQueryMap,
                options,
                collection,
                parentResourceLink,
                activityId,
                klass,
                resourceTypeEnum,
                isQueryCancelledOnTimeout);

        Flux> feedResponseFlux = executionContext.flatMap(IDocumentQueryExecutionContext::executeAsync);

        RequestOptions requestOptions = options == null? null : ImplementationBridgeHelpers
            .CosmosQueryRequestOptionsHelper
            .getCosmosQueryRequestOptionsAccessor()
            .toRequestOptions(options);

        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig =
            getEndToEndOperationLatencyPolicyConfig(requestOptions, ResourceType.Document, OperationType.Query);

        if (endToEndPolicyConfig != null && endToEndPolicyConfig.isEnabled()) {
            return getFeedResponseFluxWithTimeout(
                feedResponseFlux,
                endToEndPolicyConfig,
                options,
                isQueryCancelledOnTimeout,
                diagnosticsFactory);
        }

        return feedResponseFlux;
    }

    private  Flux> pointReadsForReadMany(
        ScopedDiagnosticsFactory diagnosticsFactory,
        Map> singleItemPartitionRequestMap,
        String resourceLink,
        CosmosQueryRequestOptions queryRequestOptions,
        Class klass) {

        // if there is any factory method being passed in, use the factory method to deserializ the object
        // else fallback to use the original way
        // typically used by spark trying to convert into SparkRowItem
        CosmosItemSerializer effectiveItemSerializer = getEffectiveItemSerializer(queryRequestOptions);

        return Flux.fromIterable(singleItemPartitionRequestMap.values())
            .flatMap(cosmosItemIdentityList -> {
                if (cosmosItemIdentityList.size() == 1) {
                    CosmosItemIdentity firstIdentity = cosmosItemIdentityList.get(0);
                    RequestOptions requestOptions = ImplementationBridgeHelpers
                        .CosmosQueryRequestOptionsHelper
                        .getCosmosQueryRequestOptionsAccessor()
                        .toRequestOptions(queryRequestOptions);
                    requestOptions.setPartitionKey(firstIdentity.getPartitionKey());
                    return this.readDocument((resourceLink + firstIdentity.getId()), requestOptions, diagnosticsFactory)
                        .flatMap(resourceResponse -> Mono.just(
                            new ImmutablePair, CosmosException>(resourceResponse, null)
                        ))
                        .onErrorResume(throwable -> {
                            Throwable unwrappedThrowable = Exceptions.unwrap(throwable);

                            if (unwrappedThrowable instanceof CosmosException) {

                                CosmosException cosmosException = (CosmosException) unwrappedThrowable;

                                int statusCode = cosmosException.getStatusCode();
                                int subStatusCode = cosmosException.getSubStatusCode();

                                if (statusCode == HttpConstants.StatusCodes.NOTFOUND && subStatusCode == HttpConstants.SubStatusCodes.UNKNOWN) {
                                    return Mono.just(new ImmutablePair, CosmosException>(null, cosmosException));
                                }
                            }

                            return Mono.error(unwrappedThrowable);
                        });
                }
                return Mono.empty();
            })
            .flatMap(resourceResponseToExceptionPair -> {

                ResourceResponse resourceResponse = resourceResponseToExceptionPair.getLeft();
                CosmosException cosmosException = resourceResponseToExceptionPair.getRight();
                FeedResponse feedResponse;

                if (cosmosException != null) {
                    feedResponse = ModelBridgeInternal.createFeedResponse(new ArrayList<>(), cosmosException.getResponseHeaders());
                    diagnosticsAccessor.addClientSideDiagnosticsToFeed(
                        feedResponse.getCosmosDiagnostics(),
                        Collections.singleton(
                            BridgeInternal.getClientSideRequestStatics(cosmosException.getDiagnostics())));
                } else {
                    CosmosItemResponse cosmosItemResponse =
                        itemResponseAccessor.createCosmosItemResponse(resourceResponse, klass, effectiveItemSerializer);

                    feedResponse = ModelBridgeInternal.createFeedResponse(
                            Arrays.asList(cosmosItemResponse.getItem()),
                            cosmosItemResponse.getResponseHeaders());

                    diagnosticsAccessor.addClientSideDiagnosticsToFeed(
                        feedResponse.getCosmosDiagnostics(),
                        Collections.singleton(
                            BridgeInternal.getClientSideRequestStatics(cosmosItemResponse.getDiagnostics())));
                }

                return Mono.just(feedResponse);
            });
    }

    @Override
    public  Flux> queryDocuments(
        String collectionLink, String query, QueryFeedOperationState state, Class classOfT) {

        return queryDocuments(collectionLink, new SqlQuerySpec(query), state, classOfT);
    }

    @Override
    public CosmosItemSerializer getEffectiveItemSerializer(CosmosItemSerializer requestOptionsItemSerializer) {
        if (requestOptionsItemSerializer != null) {
            return requestOptionsItemSerializer;
        }

        if (this.defaultCustomSerializer != null) {
            return this.defaultCustomSerializer;
        }

        return CosmosItemSerializer.DEFAULT_SERIALIZER;
    }

    private  CosmosItemSerializer getEffectiveItemSerializer(CosmosQueryRequestOptions queryRequestOptions) {

        CosmosItemSerializer requestOptionsItemSerializer =
            queryRequestOptions != null ? queryRequestOptions.getCustomItemSerializer() :  null;

        return this.getEffectiveItemSerializer(requestOptionsItemSerializer);
    }

    private  CosmosItemSerializer getEffectiveItemSerializer(CosmosItemRequestOptions itemRequestOptions) {

        CosmosItemSerializer requestOptionsItemSerializer =
            itemRequestOptions != null ? itemRequestOptions.getCustomItemSerializer() :  null;

        return this.getEffectiveItemSerializer(requestOptionsItemSerializer);
    }

    private IDocumentQueryClient documentQueryClientImpl(RxDocumentClientImpl rxDocumentClientImpl, OperationContextAndListenerTuple operationContextAndListenerTuple) {

        return new IDocumentQueryClient () {

            @Override
            public RxCollectionCache getCollectionCache() {
                return RxDocumentClientImpl.this.collectionCache;
            }

            @Override
            public RxPartitionKeyRangeCache getPartitionKeyRangeCache() {
                return RxDocumentClientImpl.this.partitionKeyRangeCache;
            }

            @Override
            public IRetryPolicyFactory getResetSessionTokenRetryPolicy() {
                return RxDocumentClientImpl.this.resetSessionTokenRetryPolicy;
            }

            @Override
            public ConsistencyLevel getDefaultConsistencyLevelAsync() {
                return RxDocumentClientImpl.this.gatewayConfigurationReader.getDefaultConsistencyLevel();
            }

            @Override
            public ConsistencyLevel getDesiredConsistencyLevelAsync() {
                // TODO Auto-generated method stub
                return RxDocumentClientImpl.this.consistencyLevel;
            }

            @Override
            public Mono executeQueryAsync(RxDocumentServiceRequest request) {
                if (operationContextAndListenerTuple == null) {
                    return RxDocumentClientImpl.this.query(request).single();
                } else {
                    final OperationListener listener =
                        operationContextAndListenerTuple.getOperationListener();
                    final OperationContext operationContext = operationContextAndListenerTuple.getOperationContext();
                    request.getHeaders().put(HttpConstants.HttpHeaders.CORRELATED_ACTIVITY_ID, operationContext.getCorrelationActivityId());
                    listener.requestListener(operationContext, request);

                    return RxDocumentClientImpl.this.query(request).single().doOnNext(
                        response -> listener.responseListener(operationContext, response)
                    ).doOnError(
                        ex -> listener.exceptionListener(operationContext, ex)
                    );
                }
            }

            @Override
            public QueryCompatibilityMode getQueryCompatibilityMode() {
                // TODO Auto-generated method stub
                return QueryCompatibilityMode.Default;
            }

            @Override
            public  Mono executeFeedOperationWithAvailabilityStrategy(
                ResourceType resourceType,
                OperationType operationType,
                Supplier retryPolicyFactory,
                RxDocumentServiceRequest req,
                BiFunction, RxDocumentServiceRequest, Mono> feedOperation,
                String collectionLink) {

                return RxDocumentClientImpl.this.executeFeedOperationWithAvailabilityStrategy(
                    resourceType,
                    operationType,
                    retryPolicyFactory,
                    req,
                    feedOperation,
                    collectionLink);
            }

            @Override
            public  CosmosItemSerializer getEffectiveItemSerializer(CosmosQueryRequestOptions queryRequestOptions) {
                return RxDocumentClientImpl.this.getEffectiveItemSerializer(queryRequestOptions);
            }

            @Override
            public Mono readFeedAsync(RxDocumentServiceRequest request) {
                // TODO Auto-generated method stub
                return null;
            }

            @Override
            public Mono populateFeedRangeHeader(RxDocumentServiceRequest request) {

                if (RxDocumentClientImpl.this.requiresFeedRangeFiltering(request)) {
                    return request
                        .getFeedRange()
                        .populateFeedRangeFilteringHeaders(RxDocumentClientImpl.this.partitionKeyRangeCache, request, RxDocumentClientImpl.this.collectionCache.resolveCollectionAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), request))
                        .flatMap(ignore -> Mono.just(request));
                } else {
                    return Mono.just(request);
                }
            }

            @Override
            public Mono addPartitionLevelUnavailableRegionsOnRequest(RxDocumentServiceRequest request, CosmosQueryRequestOptions queryRequestOptions, DocumentClientRetryPolicy documentClientRetryPolicy) {

                if (RxDocumentClientImpl.this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(request)) {

                    String collectionRid = RxDocumentClientImpl.qryOptAccessor.getCollectionRid(queryRequestOptions);

                    checkNotNull(collectionRid, "Argument 'collectionRid' cannot be null!");

                    return RxDocumentClientImpl.this.partitionKeyRangeCache.tryLookupAsync(BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics), collectionRid, null, null)
                        .flatMap(collectionRoutingMapValueHolder -> {

                            if (collectionRoutingMapValueHolder.v == null) {
                                return Mono.error(new CollectionRoutingMapNotFoundException("Argument 'collectionRoutingMapValueHolder.v' cannot be null!"));
                            }

                            RxDocumentClientImpl.this.addPartitionLevelUnavailableRegionsForFeedRequest(request, queryRequestOptions, collectionRoutingMapValueHolder.v);

                            // onBeforeSendRequest uses excluded regions to know the next location endpoint
                            // to route the request to unavailable regions are effectively excluded regions for this request
                            if (documentClientRetryPolicy != null) {
                                documentClientRetryPolicy.onBeforeSendRequest(request);
                            }

                            return Mono.just(request);
                        });
                } else {
                    return Mono.just(request);
                }
            }

            @Override
            public GlobalEndpointManager getGlobalEndpointManager() {
                return RxDocumentClientImpl.this.getGlobalEndpointManager();
            }

            @Override
            public GlobalPartitionEndpointManagerForCircuitBreaker getGlobalPartitionEndpointManagerForCircuitBreaker() {
                return RxDocumentClientImpl.this.globalPartitionEndpointManagerForCircuitBreaker;
            }
        };
    }

    @Override
    public  Flux> queryDocuments(
        String collectionLink,
        SqlQuerySpec querySpec,
        QueryFeedOperationState state,
        Class classOfT) {
        SqlQuerySpecLogger.getInstance().logQuery(querySpec);
        return createQuery(collectionLink, querySpec, state, classOfT, ResourceType.Document);
    }

    @Override
    public  Flux> queryDocumentChangeFeed(
        final DocumentCollection collection,
        final CosmosChangeFeedRequestOptions requestOptions,
        Class classOfT) {

        checkNotNull(collection, "Argument 'collection' must not be null.");


        ChangeFeedQueryImpl changeFeedQueryImpl = new ChangeFeedQueryImpl<>(
            this,
            ResourceType.Document,
            classOfT,
            collection.getAltLink(),
            collection.getResourceId(),
            requestOptions);

        return changeFeedQueryImpl.executeAsync();
    }

    @Override
    public  Flux> queryDocumentChangeFeedFromPagedFlux(DocumentCollection collection, ChangeFeedOperationState state, Class classOfT) {

        CosmosChangeFeedRequestOptions clonedOptions = changeFeedOptionsAccessor.clone(state.getChangeFeedOptions());

        CosmosChangeFeedRequestOptionsImpl optionsImpl = changeFeedOptionsAccessor.getImpl(clonedOptions);

        CosmosOperationDetails operationDetails = operationDetailsAccessor.create(optionsImpl, state.getDiagnosticsContextSnapshot());
        this.operationPolicies.forEach(policy -> {
            try {
                policy.process(operationDetails);
            } catch (RuntimeException exception) {
                logger.info("The following exception was thrown by a custom policy on changeFeed operation" + exception.getMessage());
                throw(exception);
            }
        });
        ctxAccessor.setRequestOptions(state.getDiagnosticsContextSnapshot(), optionsImpl);
        return queryDocumentChangeFeed(collection, clonedOptions, classOfT);
    }

    @Override
    public  Flux> readAllDocuments(
        String collectionLink,
        PartitionKey partitionKey,
        QueryFeedOperationState state,
        Class classOfT) {

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        if (partitionKey == null) {
            throw new IllegalArgumentException("partitionKey");
        }

        final CosmosQueryRequestOptions effectiveOptions =
            qryOptAccessor.clone(state.getQueryOptions());

        RequestOptions nonNullRequestOptions = qryOptAccessor.toRequestOptions(effectiveOptions);

        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig =
            nonNullRequestOptions.getCosmosEndToEndLatencyPolicyConfig();

        List orderedApplicableRegionsForSpeculation = getApplicableRegionsForSpeculation(
            endToEndPolicyConfig,
            ResourceType.Document,
            OperationType.Query,
            false,
            nonNullRequestOptions);

        ScopedDiagnosticsFactory diagnosticsFactory = new ScopedDiagnosticsFactory(this, false);

        if (orderedApplicableRegionsForSpeculation.size() < 2) {
            state.registerDiagnosticsFactory(
                () -> {},
                (ctx) -> diagnosticsFactory.merge(ctx));
        } else {
            state.registerDiagnosticsFactory(
                () -> diagnosticsFactory.reset(),
                (ctx) -> diagnosticsFactory.merge(ctx));
        }

        RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
            diagnosticsFactory,
            OperationType.Query,
            ResourceType.Document,
            collectionLink,
            null
        );

        // This should not get to backend
        Flux> collectionObs =
            collectionCache.resolveCollectionAsync(null, request).flux();

        return collectionObs.flatMap(documentCollectionResourceResponse -> {

            DocumentCollection collection = documentCollectionResourceResponse.v;
            if (collection == null) {
                return Mono.error(new IllegalStateException("Collection cannot be null"));
            }

            PartitionKeyDefinition pkDefinition = collection.getPartitionKey();
            List partitionKeySelectors = createPkSelectors(pkDefinition);
            SqlQuerySpec querySpec = createLogicalPartitionScanQuerySpec(partitionKey, partitionKeySelectors);

            String resourceLink = parentResourceLinkToQueryLink(collectionLink, ResourceType.Document);
            UUID activityId = randomUuid();

            final AtomicBoolean isQueryCancelledOnTimeout = new AtomicBoolean(false);

            IDocumentQueryClient queryClient = documentQueryClientImpl(RxDocumentClientImpl.this, getOperationContextAndListenerTuple(state.getQueryOptions()));

            // Trying to put this logic as low as the query pipeline
            // Since for parallelQuery, each partition will have its own request, so at this point, there will be no request associate with this retry policy.
            // For default document context, it already wired up InvalidPartitionExceptionRetry, but there is no harm to wire it again here
            InvalidPartitionExceptionRetryPolicy invalidPartitionExceptionRetryPolicy = new InvalidPartitionExceptionRetryPolicy(
                this.collectionCache,
                null,
                resourceLink,
                ModelBridgeInternal.getPropertiesFromQueryRequestOptions(effectiveOptions));

            Flux> innerFlux = ObservableHelper.fluxInlineIfPossibleAsObs(
                () -> {
                    Flux> valueHolderMono = this.partitionKeyRangeCache
                        .tryLookupAsync(
                            BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics),
                            collection.getResourceId(),
                            null,
                            null).flux();

                    return valueHolderMono.flatMap(collectionRoutingMapValueHolder -> {

                        CollectionRoutingMap routingMap = collectionRoutingMapValueHolder.v;
                        if (routingMap == null) {
                            return Mono.error(new IllegalStateException("Failed to get routing map."));
                        }

                        String effectivePartitionKeyString = PartitionKeyInternalHelper
                            .getEffectivePartitionKeyString(
                                BridgeInternal.getPartitionKeyInternal(partitionKey),
                                pkDefinition);

                        //use routing map to find the partitionKeyRangeId of each
                        // effectivePartitionKey
                        PartitionKeyRange range =
                            routingMap.getRangeByEffectivePartitionKey(effectivePartitionKeyString);

                        return createQueryInternal(
                            diagnosticsFactory,
                            resourceLink,
                            querySpec,
                            ModelBridgeInternal.setPartitionKeyRangeIdInternal(effectiveOptions, range.getId()),
                            classOfT, //Document.class
                            ResourceType.Document,
                            queryClient,
                            activityId,
                            isQueryCancelledOnTimeout);
                    });
                },
                invalidPartitionExceptionRetryPolicy);

            if (orderedApplicableRegionsForSpeculation.size() < 2) {
                return innerFlux;
            }

            return innerFlux
                .flatMap(result -> {
                    diagnosticsFactory.merge(nonNullRequestOptions);
                    return Mono.just(result);
                })
                .onErrorMap(throwable -> {
                    diagnosticsFactory.merge(nonNullRequestOptions);
                    return throwable;
                })
                .doOnCancel(() -> diagnosticsFactory.merge(nonNullRequestOptions));
        });
    }

    @Override
    public Map getQueryPlanCache() {
        return queryPlanCache;
    }

    @Override
    public Flux> readPartitionKeyRanges(final String collectionLink,
                                                                        QueryFeedOperationState state) {

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        return nonDocumentReadFeed(state, ResourceType.PartitionKeyRange, PartitionKeyRange.class,
                Utils.joinPath(collectionLink, Paths.PARTITION_KEY_RANGES_PATH_SEGMENT));
    }

    @Override
    public Flux> readPartitionKeyRanges(String collectionLink, CosmosQueryRequestOptions options) {
        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        return nonDocumentReadFeed(options, ResourceType.PartitionKeyRange, PartitionKeyRange.class,
            Utils.joinPath(collectionLink, Paths.PARTITION_KEY_RANGES_PATH_SEGMENT));
    }

    private RxDocumentServiceRequest getStoredProcedureRequest(String collectionLink, StoredProcedure storedProcedure,
                                                               RequestOptions options, OperationType operationType) {
        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }
        if (storedProcedure == null) {
            throw new IllegalArgumentException("storedProcedure");
        }

        validateResource(storedProcedure);

        String path = Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT);
        Map requestHeaders = this.getRequestHeaders(options, ResourceType.StoredProcedure, operationType);
        return RxDocumentServiceRequest.create(this, operationType,
            ResourceType.StoredProcedure, path, storedProcedure, requestHeaders, options);
    }

    private RxDocumentServiceRequest getUserDefinedFunctionRequest(String collectionLink, UserDefinedFunction udf,
                                                                   RequestOptions options, OperationType operationType) {
        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }
        if (udf == null) {
            throw new IllegalArgumentException("udf");
        }

        validateResource(udf);

        String path = Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT);
        Map requestHeaders = this.getRequestHeaders(options, ResourceType.UserDefinedFunction, operationType);
        return RxDocumentServiceRequest.create(this,
            operationType, ResourceType.UserDefinedFunction, path, udf, requestHeaders, options);
    }

    @Override
    public Mono> createStoredProcedure(String collectionLink,
                                                                               StoredProcedure storedProcedure, RequestOptions options) {
        DocumentClientRetryPolicy requestRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> createStoredProcedureInternal(collectionLink, storedProcedure, options, requestRetryPolicy), requestRetryPolicy);
    }

    private Mono> createStoredProcedureInternal(String collectionLink,
                                                                                        StoredProcedure storedProcedure, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {

            logger.debug("Creating a StoredProcedure. collectionLink: [{}], storedProcedure id [{}]",
                    collectionLink, storedProcedure.getId());
            RxDocumentServiceRequest request = getStoredProcedureRequest(collectionLink, storedProcedure, options,
                    OperationType.Create);
            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.create(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, StoredProcedure.class));

        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in creating a StoredProcedure due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> replaceStoredProcedure(StoredProcedure storedProcedure,
                                                                                RequestOptions options) {
        DocumentClientRetryPolicy requestRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replaceStoredProcedureInternal(storedProcedure, options, requestRetryPolicy), requestRetryPolicy);
    }

    private Mono> replaceStoredProcedureInternal(StoredProcedure storedProcedure,
                                                                                         RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {

            if (storedProcedure == null) {
                throw new IllegalArgumentException("storedProcedure");
            }
            logger.debug("Replacing a StoredProcedure. storedProcedure id [{}]", storedProcedure.getId());

            RxDocumentClientImpl.validateResource(storedProcedure);

            String path = Utils.joinPath(storedProcedure.getSelfLink(), null);
            Map requestHeaders = getRequestHeaders(options, ResourceType.StoredProcedure, OperationType.Replace);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Replace, ResourceType.StoredProcedure, path, storedProcedure, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.replace(request, retryPolicyInstance).map(response -> toResourceResponse(response, StoredProcedure.class));

        } catch (Exception e) {
            logger.debug("Failure in replacing a StoredProcedure due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> deleteStoredProcedure(String storedProcedureLink,
                                                                               RequestOptions options) {
        DocumentClientRetryPolicy requestRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteStoredProcedureInternal(storedProcedureLink, options, requestRetryPolicy), requestRetryPolicy);
    }

    private Mono> deleteStoredProcedureInternal(String storedProcedureLink,
                                                                                        RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {

            if (StringUtils.isEmpty(storedProcedureLink)) {
                throw new IllegalArgumentException("storedProcedureLink");
            }

            logger.debug("Deleting a StoredProcedure. storedProcedureLink [{}]", storedProcedureLink);
            String path = Utils.joinPath(storedProcedureLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.StoredProcedure, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.StoredProcedure, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, StoredProcedure.class));

        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in deleting a StoredProcedure due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> readStoredProcedure(String storedProcedureLink,
                                                                             RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readStoredProcedureInternal(storedProcedureLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readStoredProcedureInternal(String storedProcedureLink,
                                                                                      RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {

        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {

            if (StringUtils.isEmpty(storedProcedureLink)) {
                throw new IllegalArgumentException("storedProcedureLink");
            }

            logger.debug("Reading a StoredProcedure. storedProcedureLink [{}]", storedProcedureLink);
            String path = Utils.joinPath(storedProcedureLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.StoredProcedure, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.StoredProcedure, path, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, StoredProcedure.class));

        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in reading a StoredProcedure due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readStoredProcedures(String collectionLink,
                                                                    QueryFeedOperationState state) {

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        return nonDocumentReadFeed(state, ResourceType.StoredProcedure, StoredProcedure.class,
                Utils.joinPath(collectionLink, Paths.STORED_PROCEDURES_PATH_SEGMENT));
    }

    @Override
    public Flux> queryStoredProcedures(String collectionLink, String query,
                                                                     QueryFeedOperationState state) {
        return queryStoredProcedures(collectionLink, new SqlQuerySpec(query), state);
    }

    @Override
    public Flux> queryStoredProcedures(String collectionLink,
                                                                           SqlQuerySpec querySpec, QueryFeedOperationState state) {
        return createQuery(collectionLink, querySpec, state, StoredProcedure.class, ResourceType.StoredProcedure);
    }

    @Override
    public Mono executeStoredProcedure(String storedProcedureLink,
                                                                      RequestOptions options, List procedureParams) {
        DocumentClientRetryPolicy documentClientRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> executeStoredProcedureInternal(storedProcedureLink, options, procedureParams, documentClientRetryPolicy), documentClientRetryPolicy);
    }

    @Override
    public Mono executeBatchRequest(String collectionLink,
                                                         ServerBatchRequest serverBatchRequest,
                                                         RequestOptions options,
                                                         boolean disableAutomaticIdGeneration) {
        DocumentClientRetryPolicy documentClientRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        AtomicReference requestReference = new AtomicReference<>();
        RequestOptions nonNullRequestOptions = options != null ? options : new RequestOptions();
        CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig =
            getEndToEndOperationLatencyPolicyConfig(nonNullRequestOptions, ResourceType.Document, OperationType.Batch);
        ScopedDiagnosticsFactory scopedDiagnosticsFactory = new ScopedDiagnosticsFactory(this, false);
        return handleCircuitBreakingFeedbackForPointOperation(
            getPointOperationResponseMonoWithE2ETimeout(
                nonNullRequestOptions,
                endToEndPolicyConfig,
                ObservableHelper
                    .inlineIfPossibleAsObs(() -> executeBatchRequestInternal(
                        collectionLink,
                        serverBatchRequest,
                        options,
                        documentClientRetryPolicy,
                        disableAutomaticIdGeneration,
                        requestReference), documentClientRetryPolicy),
                scopedDiagnosticsFactory
            ),
            requestReference);
    }

    private Mono executeStoredProcedureInternal(String storedProcedureLink,
                                                                               RequestOptions options, List procedureParams, DocumentClientRetryPolicy retryPolicy) {

        try {
            logger.debug("Executing a StoredProcedure. storedProcedureLink [{}]", storedProcedureLink);
            String path = Utils.joinPath(storedProcedureLink, null);

            Map requestHeaders = getRequestHeaders(options, ResourceType.StoredProcedure, OperationType.ExecuteJavaScript);
            requestHeaders.put(HttpConstants.HttpHeaders.ACCEPT, RuntimeConstants.MediaTypes.JSON);

            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                    OperationType.ExecuteJavaScript,
                    ResourceType.StoredProcedure, path,
                    procedureParams != null && !procedureParams.isEmpty() ? RxDocumentClientImpl.serializeProcedureParams(procedureParams) : "",
                    requestHeaders, options);

            if (options != null) {
                request.requestContext.setExcludeRegions(options.getExcludedRegions());
                request.requestContext.setKeywordIdentifiers(options.getKeywordIdentifiers());
            }

            if (retryPolicy != null) {
                retryPolicy.onBeforeSendRequest(request);
            }

            Mono reqObs = addPartitionKeyInformation(request, null, null, options);
            return reqObs.flatMap(req -> create(request, retryPolicy, getOperationContextAndListenerTuple(options))
                    .map(response -> {
                        this.captureSessionToken(request, response);
                        return toStoredProcedureResponse(response);
                    }));

        } catch (Exception e) {
            logger.debug("Failure in executing a StoredProcedure due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    private Mono executeBatchRequestInternal(String collectionLink,
                                                                         ServerBatchRequest serverBatchRequest,
                                                                         RequestOptions options,
                                                                         DocumentClientRetryPolicy requestRetryPolicy,
                                                                         boolean disableAutomaticIdGeneration,
                                                                         AtomicReference requestReference) {

        try {
            logger.debug("Executing a Batch request with number of operations {}", serverBatchRequest.getOperations().size());

            Mono requestObs = getBatchDocumentRequest(requestRetryPolicy, collectionLink, serverBatchRequest, options, disableAutomaticIdGeneration);

            Mono responseObservable =
                requestObs.flatMap(request -> {
                    requestReference.set(request);
                    return create(request, requestRetryPolicy, getOperationContextAndListenerTuple(options));
                });

            return responseObservable
                .map(serviceResponse -> BatchResponseParser.fromDocumentServiceResponse(serviceResponse, serverBatchRequest, true));

        } catch (Exception ex) {
            logger.debug("Failure in executing a batch due to [{}]", ex.getMessage(), ex);
            return Mono.error(ex);
        }
    }

    @Override
    public Mono> createTrigger(String collectionLink, Trigger trigger,
                                                               RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> createTriggerInternal(collectionLink, trigger, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> createTriggerInternal(String collectionLink, Trigger trigger,
                                                                        RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {

            logger.debug("Creating a Trigger. collectionLink [{}], trigger id [{}]", collectionLink,
                    trigger.getId());
            RxDocumentServiceRequest request = getTriggerRequest(collectionLink, trigger, options,
                    OperationType.Create);
            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.create(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, Trigger.class));

        } catch (Exception e) {
            logger.debug("Failure in creating a Trigger due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    private RxDocumentServiceRequest getTriggerRequest(String collectionLink, Trigger trigger, RequestOptions options,
                                                       OperationType operationType) {
        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }
        if (trigger == null) {
            throw new IllegalArgumentException("trigger");
        }

        RxDocumentClientImpl.validateResource(trigger);

        String path = Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT);
        Map requestHeaders = getRequestHeaders(options, ResourceType.Trigger, operationType);
        return RxDocumentServiceRequest.create(this, operationType, ResourceType.Trigger, path,
                trigger, requestHeaders, options);
    }

    @Override
    public Mono> replaceTrigger(Trigger trigger, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replaceTriggerInternal(trigger, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> replaceTriggerInternal(Trigger trigger, RequestOptions options,
                                                                         DocumentClientRetryPolicy retryPolicyInstance) {

        try {
            if (trigger == null) {
                throw new IllegalArgumentException("trigger");
            }

            logger.debug("Replacing a Trigger. trigger id [{}]", trigger.getId());
            RxDocumentClientImpl.validateResource(trigger);

            String path = Utils.joinPath(trigger.getSelfLink(), null);
            Map requestHeaders = getRequestHeaders(options, ResourceType.Trigger, OperationType.Replace);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Replace, ResourceType.Trigger, path, trigger, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.replace(request, retryPolicyInstance).map(response -> toResourceResponse(response, Trigger.class));

        } catch (Exception e) {
            logger.debug("Failure in replacing a Trigger due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> deleteTrigger(String triggerLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteTriggerInternal(triggerLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> deleteTriggerInternal(String triggerLink, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            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, ResourceType.Trigger, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.Trigger, path, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, Trigger.class));

        } catch (Exception e) {
            logger.debug("Failure in deleting a Trigger due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> readTrigger(String triggerLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readTriggerInternal(triggerLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readTriggerInternal(String triggerLink, RequestOptions options,
                                                                      DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            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, ResourceType.Trigger, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.Trigger, path, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, Trigger.class));

        } catch (Exception e) {
            logger.debug("Failure in reading a Trigger due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readTriggers(String collectionLink, QueryFeedOperationState state) {

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        return nonDocumentReadFeed(state, ResourceType.Trigger, Trigger.class,
                Utils.joinPath(collectionLink, Paths.TRIGGERS_PATH_SEGMENT));
    }

    @Override
    public Flux> queryTriggers(String collectionLink, String query,
                                                     QueryFeedOperationState state) {
        return queryTriggers(collectionLink, new SqlQuerySpec(query), state);
    }

    @Override
    public Flux> queryTriggers(String collectionLink, SqlQuerySpec querySpec,
                                                     QueryFeedOperationState state) {
        return createQuery(collectionLink, querySpec, state, Trigger.class, ResourceType.Trigger);
    }

    @Override
    public Mono> createUserDefinedFunction(String collectionLink,
                                                                                       UserDefinedFunction udf, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> createUserDefinedFunctionInternal(collectionLink, udf, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> createUserDefinedFunctionInternal(String collectionLink,
                                                                                                UserDefinedFunction udf, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {
            logger.debug("Creating a UserDefinedFunction. collectionLink [{}], udf id [{}]", collectionLink,
                    udf.getId());
            RxDocumentServiceRequest request = getUserDefinedFunctionRequest(collectionLink, udf, options,
                    OperationType.Create);
            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.create(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, UserDefinedFunction.class));

        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in creating a UserDefinedFunction due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> replaceUserDefinedFunction(UserDefinedFunction udf,
                                                                                        RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replaceUserDefinedFunctionInternal(udf, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> replaceUserDefinedFunctionInternal(UserDefinedFunction udf,
                                                                                                 RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {
            if (udf == null) {
                throw new IllegalArgumentException("udf");
            }

            logger.debug("Replacing a UserDefinedFunction. udf id [{}]", udf.getId());
            validateResource(udf);

            String path = Utils.joinPath(udf.getSelfLink(), null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.UserDefinedFunction, OperationType.Replace);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Replace, ResourceType.UserDefinedFunction, path, udf, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.replace(request, retryPolicyInstance).map(response -> toResourceResponse(response, UserDefinedFunction.class));

        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in replacing a UserDefinedFunction due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> deleteUserDefinedFunction(String udfLink,
                                                                                       RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteUserDefinedFunctionInternal(udfLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> deleteUserDefinedFunctionInternal(String udfLink,
                                                                                                RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {
            if (StringUtils.isEmpty(udfLink)) {
                throw new IllegalArgumentException("udfLink");
            }

            logger.debug("Deleting a UserDefinedFunction. udfLink [{}]", udfLink);
            String path = Utils.joinPath(udfLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.UserDefinedFunction, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.UserDefinedFunction, path, requestHeaders, options);

            if (retryPolicyInstance != null){
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, UserDefinedFunction.class));

        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in deleting a UserDefinedFunction due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> readUserDefinedFunction(String udfLink,
                                                                                     RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readUserDefinedFunctionInternal(udfLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readUserDefinedFunctionInternal(String udfLink,
                                                                                              RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        // we are using an observable factory here
        // observable will be created fresh upon subscription
        // this is to ensure we capture most up to date information (e.g.,
        // session)
        try {
            if (StringUtils.isEmpty(udfLink)) {
                throw new IllegalArgumentException("udfLink");
            }

            logger.debug("Reading a UserDefinedFunction. udfLink [{}]", udfLink);
            String path = Utils.joinPath(udfLink, null);
            Map requestHeaders = this.getRequestHeaders(options, ResourceType.UserDefinedFunction, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.UserDefinedFunction, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, UserDefinedFunction.class));

        } catch (Exception e) {
            // this is only in trace level to capture what's going on
            logger.debug("Failure in reading a UserDefinedFunction due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readUserDefinedFunctions(String collectionLink,
                                                                                  QueryFeedOperationState state) {

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        return nonDocumentReadFeed(state, ResourceType.UserDefinedFunction, UserDefinedFunction.class,
                Utils.joinPath(collectionLink, Paths.USER_DEFINED_FUNCTIONS_PATH_SEGMENT));
    }

    @Override
    public Flux> queryUserDefinedFunctions(
        String collectionLink,
        String query,
        QueryFeedOperationState state) {

        return queryUserDefinedFunctions(collectionLink, new SqlQuerySpec(query), state);
    }

    @Override
    public Flux> queryUserDefinedFunctions(
        String collectionLink,
        SqlQuerySpec querySpec,
        QueryFeedOperationState state) {

        return createQuery(collectionLink, querySpec, state, UserDefinedFunction.class, ResourceType.UserDefinedFunction);
    }

    @Override
    public Mono> readConflict(String conflictLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readConflictInternal(conflictLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readConflictInternal(String conflictLink, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {

        try {
            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, ResourceType.Conflict, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.Conflict, path, requestHeaders, options);

            Mono reqObs = addPartitionKeyInformation(request, null, null, options);

            return reqObs.flatMap(req -> {
                if (retryPolicyInstance != null) {
                    retryPolicyInstance.onBeforeSendRequest(request);
                }
                return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, Conflict.class));
            });

        } catch (Exception e) {
            logger.debug("Failure in reading a Conflict due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readConflicts(String collectionLink, QueryFeedOperationState state) {

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        return nonDocumentReadFeed(state, ResourceType.Conflict, Conflict.class,
                Utils.joinPath(collectionLink, Paths.CONFLICTS_PATH_SEGMENT));
    }

    @Override
    public Flux> queryConflicts(String collectionLink, String query,
                                                       QueryFeedOperationState state) {
        return queryConflicts(collectionLink, new SqlQuerySpec(query), state);
    }

    @Override
    public Flux> queryConflicts(String collectionLink, SqlQuerySpec querySpec,
                                                       QueryFeedOperationState state) {
        return createQuery(collectionLink, querySpec, state, Conflict.class, ResourceType.Conflict);
    }

    @Override
    public Mono> deleteConflict(String conflictLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteConflictInternal(conflictLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> deleteConflictInternal(String conflictLink, RequestOptions options,
                                                                          DocumentClientRetryPolicy retryPolicyInstance) {

        try {
            if (StringUtils.isEmpty(conflictLink)) {
                throw new IllegalArgumentException("conflictLink");
            }

            logger.debug("Deleting a Conflict. conflictLink [{}]", conflictLink);
            String path = Utils.joinPath(conflictLink, null);
            Map requestHeaders = getRequestHeaders(options, ResourceType.Conflict, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.Conflict, path, requestHeaders, options);

            Mono reqObs = addPartitionKeyInformation(request, null, null, options);
            return reqObs.flatMap(req -> {
                if (retryPolicyInstance != null) {
                    retryPolicyInstance.onBeforeSendRequest(request);
                }

                return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, Conflict.class));
            });

        } catch (Exception e) {
            logger.debug("Failure in deleting a Conflict due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> createUser(String databaseLink, User user, RequestOptions options) {
        DocumentClientRetryPolicy documentClientRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> createUserInternal(databaseLink, user, options, documentClientRetryPolicy), documentClientRetryPolicy);
    }

    private Mono> createUserInternal(String databaseLink, User user, RequestOptions options, DocumentClientRetryPolicy documentClientRetryPolicy) {
        try {
            logger.debug("Creating a User. databaseLink [{}], user id [{}]", databaseLink, user.getId());
            RxDocumentServiceRequest request = getUserRequest(databaseLink, user, options, OperationType.Create);
            return this.create(request, documentClientRetryPolicy, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, User.class));

        } catch (Exception e) {
            logger.debug("Failure in creating a User due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> upsertUser(String databaseLink, User user, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> upsertUserInternal(databaseLink, user, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> upsertUserInternal(String databaseLink, User user, RequestOptions options,
                                                                  DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            logger.debug("Upserting a User. databaseLink [{}], user id [{}]", databaseLink, user.getId());
            RxDocumentServiceRequest request = getUserRequest(databaseLink, user, options, OperationType.Upsert);
            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.upsert(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, User.class));

        } catch (Exception e) {
            logger.debug("Failure in upserting a User due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    private RxDocumentServiceRequest getUserRequest(String databaseLink, User user, RequestOptions options,
                                                    OperationType operationType) {
        if (StringUtils.isEmpty(databaseLink)) {
            throw new IllegalArgumentException("databaseLink");
        }
        if (user == null) {
            throw new IllegalArgumentException("user");
        }

        RxDocumentClientImpl.validateResource(user);

        String path = Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT);
        Map requestHeaders = getRequestHeaders(options, ResourceType.User, operationType);
        return RxDocumentServiceRequest.create(this,
            operationType, ResourceType.User, path, user, requestHeaders, options);
    }

    @Override
    public Mono> replaceUser(User user, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replaceUserInternal(user, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> replaceUserInternal(User user, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (user == null) {
                throw new IllegalArgumentException("user");
            }
            logger.debug("Replacing a User. user id [{}]", user.getId());
            RxDocumentClientImpl.validateResource(user);

            String path = Utils.joinPath(user.getSelfLink(), null);
            Map requestHeaders = getRequestHeaders(options, ResourceType.User, OperationType.Replace);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Replace, ResourceType.User, path, user, requestHeaders, options);
            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.replace(request, retryPolicyInstance).map(response -> toResourceResponse(response, User.class));

        } catch (Exception e) {
            logger.debug("Failure in replacing a User due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }


    public Mono> deleteUser(String userLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance =  this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deleteUserInternal(userLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> deleteUserInternal(String userLink, RequestOptions options,
                                                                  DocumentClientRetryPolicy retryPolicyInstance) {

        try {
            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, ResourceType.User, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.User, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, User.class));

        } catch (Exception e) {
            logger.debug("Failure in deleting a User due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }
    @Override
    public Mono> readUser(String userLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readUserInternal(userLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readUserInternal(String userLink, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            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, ResourceType.User, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.User, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }
            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, User.class));

        } catch (Exception e) {
            logger.debug("Failure in reading a User due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readUsers(String databaseLink, QueryFeedOperationState state) {

        if (StringUtils.isEmpty(databaseLink)) {
            throw new IllegalArgumentException("databaseLink");
        }

        return nonDocumentReadFeed(state, ResourceType.User, User.class,
                Utils.joinPath(databaseLink, Paths.USERS_PATH_SEGMENT));
    }

    @Override
    public Flux> queryUsers(String databaseLink, String query, QueryFeedOperationState state) {
        return queryUsers(databaseLink, new SqlQuerySpec(query), state);
    }

    @Override
    public Flux> queryUsers(String databaseLink, SqlQuerySpec querySpec,
                                               QueryFeedOperationState state) {
        return createQuery(databaseLink, querySpec, state, User.class, ResourceType.User);
    }

    @Override
    public Mono> readClientEncryptionKey(String clientEncryptionKeyLink,
                                                                RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readClientEncryptionKeyInternal(clientEncryptionKeyLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readClientEncryptionKeyInternal(String clientEncryptionKeyLink, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (StringUtils.isEmpty(clientEncryptionKeyLink)) {
                throw new IllegalArgumentException("clientEncryptionKeyLink");
            }
            logger.debug("Reading a client encryption key. clientEncryptionKeyLink [{}]", clientEncryptionKeyLink);
            String path = Utils.joinPath(clientEncryptionKeyLink, null);
            Map requestHeaders = getRequestHeaders(options, ResourceType.ClientEncryptionKey, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.ClientEncryptionKey, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }
            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, ClientEncryptionKey.class));

        } catch (Exception e) {
            logger.debug("Failure in reading a client encryption key due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> createClientEncryptionKey(String databaseLink,
     ClientEncryptionKey clientEncryptionKey, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> createClientEncryptionKeyInternal(databaseLink, clientEncryptionKey, options, retryPolicyInstance), retryPolicyInstance);

    }

    private Mono> createClientEncryptionKeyInternal(String databaseLink, ClientEncryptionKey clientEncryptionKey, RequestOptions options, DocumentClientRetryPolicy documentClientRetryPolicy) {
        try {
            logger.debug("Creating a client encryption key. databaseLink [{}], clientEncryptionKey id [{}]", databaseLink, clientEncryptionKey.getId());
            RxDocumentServiceRequest request = getClientEncryptionKeyRequest(databaseLink, clientEncryptionKey, options, OperationType.Create);
            return this.create(request, documentClientRetryPolicy, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, ClientEncryptionKey.class));

        } catch (Exception e) {
            logger.debug("Failure in creating a client encryption key due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    private RxDocumentServiceRequest getClientEncryptionKeyRequest(String databaseLink, ClientEncryptionKey clientEncryptionKey, RequestOptions options,
                                                    OperationType operationType) {
        if (StringUtils.isEmpty(databaseLink)) {
            throw new IllegalArgumentException("databaseLink");
        }
        if (clientEncryptionKey == null) {
            throw new IllegalArgumentException("clientEncryptionKey");
        }

        RxDocumentClientImpl.validateResource(clientEncryptionKey);

        String path = Utils.joinPath(databaseLink, Paths.CLIENT_ENCRYPTION_KEY_PATH_SEGMENT);
        Map requestHeaders = getRequestHeaders(options, ResourceType.ClientEncryptionKey, operationType);
        return RxDocumentServiceRequest.create(this,
            operationType, ResourceType.ClientEncryptionKey, path, clientEncryptionKey, requestHeaders, options);
    }

    @Override
    public Mono> replaceClientEncryptionKey(ClientEncryptionKey clientEncryptionKey,
                                                                                  String nameBasedLink,
                                                                                  RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replaceClientEncryptionKeyInternal(clientEncryptionKey,
            nameBasedLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> replaceClientEncryptionKeyInternal(ClientEncryptionKey clientEncryptionKey, String nameBasedLink, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (clientEncryptionKey == null) {
                throw new IllegalArgumentException("clientEncryptionKey");
            }
            logger.debug("Replacing a clientEncryptionKey. clientEncryptionKey id [{}]", clientEncryptionKey.getId());
            RxDocumentClientImpl.validateResource(clientEncryptionKey);

            String path = Utils.joinPath(nameBasedLink, null);
            //String path = Utils.joinPath(clientEncryptionKey.getSelfLink(), null); TODO need to check with BE service
            Map requestHeaders = getRequestHeaders(options, ResourceType.ClientEncryptionKey,
             OperationType.Replace);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Replace, ResourceType.ClientEncryptionKey, path, clientEncryptionKey, requestHeaders,
                 options);
            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.replace(request, retryPolicyInstance).map(response -> toResourceResponse(response, ClientEncryptionKey.class));

        } catch (Exception e) {
            logger.debug("Failure in replacing a clientEncryptionKey due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readClientEncryptionKeys(
        String databaseLink,
        QueryFeedOperationState state) {
        if (StringUtils.isEmpty(databaseLink)) {
            throw new IllegalArgumentException("databaseLink");
        }

        return nonDocumentReadFeed(state, ResourceType.ClientEncryptionKey, ClientEncryptionKey.class,
            Utils.joinPath(databaseLink, Paths.CLIENT_ENCRYPTION_KEY_PATH_SEGMENT));
    }

    @Override
    public Flux> queryClientEncryptionKeys(
        String databaseLink,
        SqlQuerySpec querySpec,
        QueryFeedOperationState state) {
        return createQuery(databaseLink, querySpec, state, ClientEncryptionKey.class, ResourceType.ClientEncryptionKey);
    }

    @Override
    public Mono> createPermission(String userLink, Permission permission,
                                                                     RequestOptions options) {
        DocumentClientRetryPolicy documentClientRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> createPermissionInternal(userLink, permission, options, documentClientRetryPolicy), this.resetSessionTokenRetryPolicy.getRequestPolicy(null));
    }

    private Mono> createPermissionInternal(String userLink, Permission permission,
                                                                              RequestOptions options, DocumentClientRetryPolicy documentClientRetryPolicy) {

        try {
            logger.debug("Creating a Permission. userLink [{}], permission id [{}]", userLink, permission.getId());
            RxDocumentServiceRequest request = getPermissionRequest(userLink, permission, options,
                    OperationType.Create);
            return this.create(request, documentClientRetryPolicy, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, Permission.class));

        } catch (Exception e) {
            logger.debug("Failure in creating a Permission due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> upsertPermission(String userLink, Permission permission,
                                                                     RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> upsertPermissionInternal(userLink, permission, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> upsertPermissionInternal(String userLink, Permission permission,
                                                                              RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {

        try {
            logger.debug("Upserting a Permission. userLink [{}], permission id [{}]", userLink, permission.getId());
            RxDocumentServiceRequest request = getPermissionRequest(userLink, permission, options,
                    OperationType.Upsert);
            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.upsert(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, Permission.class));

        } catch (Exception e) {
            logger.debug("Failure in upserting a Permission due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    private RxDocumentServiceRequest getPermissionRequest(String userLink, Permission permission,
                                                          RequestOptions options, OperationType operationType) {
        if (StringUtils.isEmpty(userLink)) {
            throw new IllegalArgumentException("userLink");
        }
        if (permission == null) {
            throw new IllegalArgumentException("permission");
        }

        RxDocumentClientImpl.validateResource(permission);

        String path = Utils.joinPath(userLink, Paths.PERMISSIONS_PATH_SEGMENT);
        Map requestHeaders = getRequestHeaders(options, ResourceType.Permission, operationType);
        return RxDocumentServiceRequest.create(this,
            operationType, ResourceType.Permission, path, permission, requestHeaders, options);
    }

    @Override
    public Mono> replacePermission(Permission permission, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replacePermissionInternal(permission, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> replacePermissionInternal(Permission permission, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (permission == null) {
                throw new IllegalArgumentException("permission");
            }
            logger.debug("Replacing a Permission. permission id [{}]", permission.getId());
            RxDocumentClientImpl.validateResource(permission);

            String path = Utils.joinPath(permission.getSelfLink(), null);
            Map requestHeaders = getRequestHeaders(options, ResourceType.Permission, OperationType.Replace);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Replace, ResourceType.Permission, path, permission, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.replace(request, retryPolicyInstance).map(response -> toResourceResponse(response, Permission.class));

        } catch (Exception e) {
            logger.debug("Failure in replacing a Permission due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> deletePermission(String permissionLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> deletePermissionInternal(permissionLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> deletePermissionInternal(String permissionLink, RequestOptions options,
                                                                              DocumentClientRetryPolicy retryPolicyInstance) {

        try {
            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, ResourceType.Permission, OperationType.Delete);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Delete, ResourceType.Permission, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.delete(request, retryPolicyInstance, getOperationContextAndListenerTuple(options)).map(response -> toResourceResponse(response, Permission.class));

        } catch (Exception e) {
            logger.debug("Failure in deleting a Permission due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> readPermission(String permissionLink, RequestOptions options) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readPermissionInternal(permissionLink, options, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readPermissionInternal(String permissionLink, RequestOptions options, DocumentClientRetryPolicy retryPolicyInstance ) {
        try {
            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, ResourceType.Permission, OperationType.Read);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.Permission, path, requestHeaders, options);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }
            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, Permission.class));

        } catch (Exception e) {
            logger.debug("Failure in reading a Permission due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readPermissions(String userLink, QueryFeedOperationState state) {

        if (StringUtils.isEmpty(userLink)) {
            throw new IllegalArgumentException("userLink");
        }

        return nonDocumentReadFeed(state, ResourceType.Permission, Permission.class,
                Utils.joinPath(userLink, Paths.PERMISSIONS_PATH_SEGMENT));
    }

    @Override
    public Flux> queryPermissions(String userLink, String query,
                                                           QueryFeedOperationState state) {
        return queryPermissions(userLink, new SqlQuerySpec(query), state);
    }

    @Override
    public Flux> queryPermissions(String userLink, SqlQuerySpec querySpec,
                                                           QueryFeedOperationState state) {
        return createQuery(userLink, querySpec, state, Permission.class, ResourceType.Permission);
    }

    @Override
    public Mono> replaceOffer(Offer offer) {
        DocumentClientRetryPolicy documentClientRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> replaceOfferInternal(offer, documentClientRetryPolicy), documentClientRetryPolicy);
    }

    private Mono> replaceOfferInternal(Offer offer, DocumentClientRetryPolicy documentClientRetryPolicy) {
        try {
            if (offer == null) {
                throw new IllegalArgumentException("offer");
            }
            logger.debug("Replacing an Offer. offer id [{}]", offer.getId());
            RxDocumentClientImpl.validateResource(offer);

            String path = Utils.joinPath(offer.getSelfLink(), null);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this, OperationType.Replace,
                    ResourceType.Offer, path, offer, null, null);
            return this.replace(request, documentClientRetryPolicy).map(response -> toResourceResponse(response, Offer.class));

        } catch (Exception e) {
            logger.debug("Failure in replacing an Offer due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Mono> readOffer(String offerLink) {
        DocumentClientRetryPolicy retryPolicyInstance = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> readOfferInternal(offerLink, retryPolicyInstance), retryPolicyInstance);
    }

    private Mono> readOfferInternal(String offerLink, DocumentClientRetryPolicy retryPolicyInstance) {
        try {
            if (StringUtils.isEmpty(offerLink)) {
                throw new IllegalArgumentException("offerLink");
            }
            logger.debug("Reading an Offer. offerLink [{}]", offerLink);
            String path = Utils.joinPath(offerLink, null);
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.Offer, path, (HashMap)null, null);

            if (retryPolicyInstance != null) {
                retryPolicyInstance.onBeforeSendRequest(request);
            }

            return this.read(request, retryPolicyInstance).map(response -> toResourceResponse(response, Offer.class));

        } catch (Exception e) {
            logger.debug("Failure in reading an Offer due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    @Override
    public Flux> readOffers(QueryFeedOperationState state) {
        return nonDocumentReadFeed(state, ResourceType.Offer, Offer.class,
                Utils.joinPath(Paths.OFFERS_PATH_SEGMENT, null));
    }

    private  Flux> nonDocumentReadFeed(
        QueryFeedOperationState state,
        ResourceType resourceType,
        Class klass,
        String resourceLink) {

        return nonDocumentReadFeed(state.getQueryOptions(), resourceType, klass, resourceLink);
    }

    private  Flux> nonDocumentReadFeed(
        CosmosQueryRequestOptions options,
        ResourceType resourceType,
        Class klass,
        String resourceLink) {
        DocumentClientRetryPolicy retryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.fluxInlineIfPossibleAsObs(
            () -> nonDocumentReadFeedInternal(options, resourceType, klass, resourceLink, retryPolicy),
            retryPolicy);
    }

    private  Flux> nonDocumentReadFeedInternal(
        CosmosQueryRequestOptions options,
        ResourceType resourceType,
        Class klass,
        String resourceLink,
        DocumentClientRetryPolicy retryPolicy) {

        final CosmosQueryRequestOptions nonNullOptions = options != null ? options : new CosmosQueryRequestOptions();
        Integer maxItemCount = ModelBridgeInternal.getMaxItemCountFromQueryRequestOptions(nonNullOptions);
        int maxPageSize = maxItemCount != null ? maxItemCount : -1;

        assert(resourceType != ResourceType.Document);
        // readFeed is only used for non-document operations - no need to wire up hedging
        BiFunction createRequestFunc = (continuationToken, pageSize) -> {
            Map requestHeaders = new HashMap<>();
            if (continuationToken != null) {
                requestHeaders.put(HttpConstants.HttpHeaders.CONTINUATION, continuationToken);
            }
            requestHeaders.put(HttpConstants.HttpHeaders.PAGE_SIZE, Integer.toString(pageSize));
            RxDocumentServiceRequest request =  RxDocumentServiceRequest.create(this,
                OperationType.ReadFeed, resourceType, resourceLink, requestHeaders, nonNullOptions);
            retryPolicy.onBeforeSendRequest(request);
            return request;
        };

        Function>> executeFunc =
            request -> readFeed(request)
                .map(response -> feedResponseAccessor.createFeedResponse(
                                    response,
                                    CosmosItemSerializer.DEFAULT_SERIALIZER,
                                    klass));

        return Paginator
            .getPaginatedQueryResultAsObservable(
                nonNullOptions,
                createRequestFunc,
                executeFunc,
                maxPageSize,
                this.globalEndpointManager,
                this.globalPartitionEndpointManagerForCircuitBreaker);
    }

    @Override
    public Flux> queryOffers(String query, QueryFeedOperationState state) {
        return queryOffers(new SqlQuerySpec(query), state);
    }

    @Override
    public Flux> queryOffers(SqlQuerySpec querySpec, QueryFeedOperationState state) {
        return createQuery(null, querySpec, state, Offer.class, ResourceType.Offer);
    }

    @Override
    public Mono getDatabaseAccount() {
        DocumentClientRetryPolicy documentClientRetryPolicy = this.resetSessionTokenRetryPolicy.getRequestPolicy(null);
        return ObservableHelper.inlineIfPossibleAsObs(() -> getDatabaseAccountInternal(documentClientRetryPolicy),
         documentClientRetryPolicy);
    }

    private Mono getDatabaseAccountInternal(DocumentClientRetryPolicy documentClientRetryPolicy) {
        try {
            logger.debug("Getting Database Account");
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                    OperationType.Read,
                    ResourceType.DatabaseAccount, "", // path
                    (HashMap) null,
                    null);
            return this.read(request, documentClientRetryPolicy).map(ModelBridgeInternal::toDatabaseAccount);

        } catch (Exception e) {
            logger.debug("Failure in getting Database Account due to [{}]", e.getMessage(), e);
            return Mono.error(e);
        }
    }

    public ISessionContainer getSession() {
        return this.sessionContainer;
    }

    public void setSession(ISessionContainer sessionContainer) {
        this.sessionContainer = sessionContainer;
    }

    public CosmosAsyncClient getCachedCosmosAsyncClientSnapshot() {
        return cachedCosmosAsyncClientSnapshot.get();
    }

    @Override
    public RxClientCollectionCache getCollectionCache() {
        return this.collectionCache;
    }

    @Override
    public RxPartitionKeyRangeCache getPartitionKeyRangeCache() {
        return partitionKeyRangeCache;
    }

    @Override
    public GlobalEndpointManager getGlobalEndpointManager() {
        return this.globalEndpointManager;
    }

    @Override
    public GlobalPartitionEndpointManagerForCircuitBreaker getGlobalPartitionEndpointManagerForCircuitBreaker() {
        return this.globalPartitionEndpointManagerForCircuitBreaker;
    }

    @Override
    public AddressSelector getAddressSelector() {
        return new AddressSelector(this.addressResolver, this.configs.getProtocol());
    }

    public Flux getDatabaseAccountFromEndpoint(URI endpoint) {
        return Flux.defer(() -> {
            RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
                OperationType.Read, ResourceType.DatabaseAccount, "", null, (Object) null);
            return this.populateHeadersAsync(request, RequestVerb.GET)
                .flatMap(requestPopulated -> {

                    requestPopulated.setEndpointOverride(endpoint);
                    return this.gatewayProxy.processMessage(requestPopulated).doOnError(e -> {
                        String message = String.format("Failed to retrieve database account information. %s",
                            e.getCause() != null
                                ? e.getCause().toString()
                                : e.toString());
                        logger.warn(message);
                    }).map(rsp -> rsp.getResource(DatabaseAccount.class))
                        .doOnNext(databaseAccount ->
                            this.useMultipleWriteLocations = this.connectionPolicy.isMultipleWriteRegionsEnabled()
                            && BridgeInternal.isEnableMultipleWriteLocations(databaseAccount));
                });
        });
    }

    /**
     * Certain requests must be routed through gateway even when the client connectivity mode is direct.
     *
     * @param request
     * @return RxStoreModel
     */
    private RxStoreModel getStoreProxy(RxDocumentServiceRequest request) {
        // If a request is configured to always use GATEWAY mode(in some cases when targeting .NET Core)
        // we return the GATEWAY store model
        if (request.useGatewayMode) {
            return this.gatewayProxy;
        }

        ResourceType resourceType = request.getResourceType();
        OperationType operationType = request.getOperationType();

        if (resourceType == ResourceType.Offer ||
            resourceType == ResourceType.ClientEncryptionKey ||
            resourceType.isScript() && operationType != OperationType.ExecuteJavaScript ||
            resourceType == ResourceType.PartitionKeyRange ||
            resourceType == ResourceType.PartitionKey && operationType == OperationType.Delete) {
            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 {
            if ((operationType == OperationType.Query ||
                operationType == OperationType.SqlQuery ||
                operationType == OperationType.ReadFeed) &&
                    Utils.isCollectionChild(request.getResourceType())) {
                // Go to gateway only when partition key range and partition key are not set. This should be very rare
                if (request.getPartitionKeyRangeIdentity() == null &&
                        request.getHeaders().get(HttpConstants.HttpHeaders.PARTITION_KEY) == null) {
                    return this.gatewayProxy;
                }
            }

            return this.storeModel;
        }
    }

    @Override
    public void close() {
        logger.info("Attempting to close client {}", this.clientId);
        if (!closed.getAndSet(true)) {
            activeClientsCnt.decrementAndGet();
            logger.info("Shutting down ...");

            if (this.globalPartitionEndpointManagerForCircuitBreaker != null) {
                logger.info("Closing globalPartitionEndpointManagerForCircuitBreaker...");
                LifeCycleUtils.closeQuietly(this.globalPartitionEndpointManagerForCircuitBreaker);
            }

            logger.info("Closing Global Endpoint Manager ...");
            LifeCycleUtils.closeQuietly(this.globalEndpointManager);
            logger.info("Closing StoreClientFactory ...");
            LifeCycleUtils.closeQuietly(this.storeClientFactory);
            logger.info("Shutting down reactorHttpClient ...");
            LifeCycleUtils.closeQuietly(this.reactorHttpClient);
            logger.info("Shutting down CpuMonitor ...");
            CpuMemoryMonitor.unregister(this);

            if (this.throughputControlEnabled.get()) {
                logger.info("Closing ThroughputControlStore ...");
                this.throughputControlStore.close();
            }

            logger.info("Shutting down completed.");
        } else {
            logger.warn("Already shutdown!");
        }
    }
    @Override
    public synchronized void enableThroughputControlGroup(ThroughputControlGroupInternal group, Mono throughputQueryMono) {
        checkNotNull(group, "Throughput control group can not be null");

        if (this.throughputControlEnabled.compareAndSet(false, true)) {
            this.throughputControlStore =
                new ThroughputControlStore(
                    this.collectionCache,
                    this.connectionPolicy.getConnectionMode(),
                    this.partitionKeyRangeCache);

            if (ConnectionMode.DIRECT == this.connectionPolicy.getConnectionMode()) {
                this.storeModel.enableThroughputControl(throughputControlStore);
            } else {
                this.gatewayProxy.enableThroughputControl(throughputControlStore);
            }
        }

        this.throughputControlStore.enableThroughputControlGroup(group, throughputQueryMono);
    }

    @Override
    public Flux submitOpenConnectionTasksAndInitCaches(CosmosContainerProactiveInitConfig proactiveContainerInitConfig) {
        return this.storeModel.submitOpenConnectionTasksAndInitCaches(proactiveContainerInitConfig);
    }

    @Override
    public ConsistencyLevel getDefaultConsistencyLevelOfAccount() {
        return this.gatewayConfigurationReader.getDefaultConsistencyLevel();
    }

    /***
     * Configure fault injector provider.
     *
     * @param injectorProvider the fault injector provider.
     */
    @Override
    public void configureFaultInjectorProvider(IFaultInjectorProvider injectorProvider) {
        checkNotNull(injectorProvider, "Argument 'injectorProvider' can not be null");

        if (this.connectionPolicy.getConnectionMode() == ConnectionMode.DIRECT) {
            this.storeModel.configureFaultInjectorProvider(injectorProvider, this.configs);
            this.addressResolver.configureFaultInjectorProvider(injectorProvider, this.configs);
        }

        this.gatewayProxy.configureFaultInjectorProvider(injectorProvider, this.configs);
    }

    @Override
    public void recordOpenConnectionsAndInitCachesCompleted(List cosmosContainerIdentities) {
        this.storeModel.recordOpenConnectionsAndInitCachesCompleted(cosmosContainerIdentities);
    }

    @Override
    public void recordOpenConnectionsAndInitCachesStarted(List cosmosContainerIdentities) {
        this.storeModel.recordOpenConnectionsAndInitCachesStarted(cosmosContainerIdentities);
    }

    @Override
    public String getMasterKeyOrResourceToken() {
        return this.masterKeyOrResourceToken;
    }

    private static SqlQuerySpec createLogicalPartitionScanQuerySpec(
        PartitionKey partitionKey,
        List partitionKeySelectors) {

        StringBuilder queryStringBuilder = new StringBuilder();
        List parameters = new ArrayList<>();

        queryStringBuilder.append("SELECT * FROM c WHERE");
        Object[] pkValues = ModelBridgeInternal.getPartitionKeyInternal(partitionKey).toObjectArray();
        String pkParamNamePrefix = "@pkValue";
        for (int i = 0; i < pkValues.length; i++) {
            StringBuilder subQueryStringBuilder = new StringBuilder();
            String sqlParameterName = pkParamNamePrefix + i;

            if (i > 0) {
                subQueryStringBuilder.append(" AND ");
            }
            subQueryStringBuilder.append(" c");
            subQueryStringBuilder.append(partitionKeySelectors.get(i));
            subQueryStringBuilder.append((" = "));
            subQueryStringBuilder.append(sqlParameterName);

            parameters.add(new SqlParameter(sqlParameterName, pkValues[i]));
            queryStringBuilder.append(subQueryStringBuilder);
        }

        return new SqlQuerySpec(queryStringBuilder.toString(), parameters);
    }

    @Override
    public Mono> getFeedRanges(String collectionLink, boolean forceRefresh) {
        InvalidPartitionExceptionRetryPolicy invalidPartitionExceptionRetryPolicy = new InvalidPartitionExceptionRetryPolicy(
            this.collectionCache,
            null,
            collectionLink,
            new HashMap<>());

        RxDocumentServiceRequest request = RxDocumentServiceRequest.create(
            this,
            OperationType.Query,
            ResourceType.Document,
            collectionLink,
            null);

        invalidPartitionExceptionRetryPolicy.onBeforeSendRequest(request);

        return ObservableHelper.inlineIfPossibleAsObs(
            () -> getFeedRangesInternal(request, collectionLink, forceRefresh),
            invalidPartitionExceptionRetryPolicy);
    }

    private Mono> getFeedRangesInternal(
        RxDocumentServiceRequest request,
        String collectionLink,
        boolean forceRefresh) {

        logger.debug("getFeedRange collectionLink=[{}] - forceRefresh={}", collectionLink, forceRefresh);

        if (StringUtils.isEmpty(collectionLink)) {
            throw new IllegalArgumentException("collectionLink");
        }

        Mono> collectionObs = collectionCache.resolveCollectionAsync(null,
            request);

        return collectionObs.flatMap(documentCollectionResourceResponse -> {
            final DocumentCollection collection = documentCollectionResourceResponse.v;
            if (collection == null) {
                return Mono.error(new IllegalStateException("Collection cannot be null"));
            }

            Mono>> valueHolderMono = partitionKeyRangeCache
                .tryGetOverlappingRangesAsync(
                    BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics),
                    collection.getResourceId(),
                    RANGE_INCLUDING_ALL_PARTITION_KEY_RANGES,
                    forceRefresh,
                    null);

            return valueHolderMono.map(partitionKeyRangeList -> toFeedRanges(partitionKeyRangeList, request));
        });
    }

    private static List toFeedRanges(
        Utils.ValueHolder> partitionKeyRangeListValueHolder, RxDocumentServiceRequest request) {
        final List partitionKeyRangeList = partitionKeyRangeListValueHolder.v;
        if (partitionKeyRangeList == null) {
            request.forceNameCacheRefresh = true;
            throw new InvalidPartitionException();
        }

        List feedRanges = new ArrayList<>();
        partitionKeyRangeList.forEach(pkRange -> feedRanges.add(toFeedRange(pkRange)));

        return feedRanges;
    }

    private static FeedRange toFeedRange(PartitionKeyRange pkRange) {
        return new FeedRangeEpkImpl(pkRange.toRange());
    }

    /**
     * Creates a type 4 (pseudo randomly generated) UUID.
     * 

* The {@link UUID} is generated using a non-cryptographically strong pseudo random number generator. * * @return A randomly generated {@link UUID}. */ public static UUID randomUuid() { // Note: Copied from CoreUtils return randomUuid(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong()); } static UUID randomUuid(long msb, long lsb) { msb &= 0xffffffffffff0fffL; // Clear the UUID version. msb |= 0x0000000000004000L; // Set the UUID version to 4. lsb &= 0x3fffffffffffffffL; // Clear the variant. lsb |= 0x8000000000000000L; // Set the variant to IETF. // Use new UUID(long, long) instead of UUID.randomUUID as UUID.randomUUID may be blocking. // For environments using Reactor's BlockHound this will raise an exception if called in non-blocking threads. return new UUID(msb, lsb); } public void addPartitionLevelUnavailableRegionsForRequest( RxDocumentServiceRequest request, RequestOptions options, CollectionRoutingMap collectionRoutingMap, DocumentClientRetryPolicy documentClientRetryPolicy) { checkNotNull(request, "Argument 'request' cannot be null!"); if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(request)) { checkNotNull(options, "Argument 'options' cannot be null!"); checkNotNull(options.getPartitionKeyDefinition(), "Argument 'partitionKeyDefinition' within options cannot be null!"); checkNotNull(collectionRoutingMap, "Argument 'collectionRoutingMap' cannot be null!"); PartitionKeyRange resolvedPartitionKeyRange = null; PartitionKeyDefinition partitionKeyDefinition = options.getPartitionKeyDefinition(); PartitionKeyInternal partitionKeyInternal = request.getPartitionKeyInternal(); if (partitionKeyInternal != null) { String effectivePartitionKeyString = PartitionKeyInternalHelper.getEffectivePartitionKeyString(partitionKeyInternal, partitionKeyDefinition); resolvedPartitionKeyRange = collectionRoutingMap.getRangeByEffectivePartitionKey(effectivePartitionKeyString); // cache the effective partition key if possible - can be a bottleneck, // since it is also recomputed in AddressResolver request.setEffectivePartitionKey(effectivePartitionKeyString); } else if (request.getPartitionKeyRangeIdentity() != null) { resolvedPartitionKeyRange = collectionRoutingMap.getRangeByPartitionKeyRangeId(request.getPartitionKeyRangeIdentity().getPartitionKeyRangeId()); } checkNotNull(resolvedPartitionKeyRange, "resolvedPartitionKeyRange cannot be null!"); checkNotNull(this.globalPartitionEndpointManagerForCircuitBreaker, "globalPartitionEndpointManagerForCircuitBreaker cannot be null!"); // setting it here in case request.requestContext.resolvedPartitionKeyRange // is not assigned in either GlobalAddressResolver / RxGatewayStoreModel (possible if there are Gateway timeouts) // and circuit breaker also kicks in to mark a failure resolvedPartitionKeyRange (will result in NullPointerException and will // help failover as well) // also resolvedPartitionKeyRange will be overridden in GlobalAddressResolver / RxGatewayStoreModel irrespective // so staleness is not an issue (after doing a validation of parent-child relationship b/w initial and new partitionKeyRange) request.requestContext.resolvedPartitionKeyRange = resolvedPartitionKeyRange; List unavailableRegionsForPartition = this.globalPartitionEndpointManagerForCircuitBreaker.getUnavailableRegionsForPartitionKeyRange( request.getResourceId(), resolvedPartitionKeyRange, request.getOperationType()); request.requestContext.setUnavailableRegionsForPartition(unavailableRegionsForPartition); // onBeforeSendRequest uses excluded regions to know the next location endpoint // to route the request to unavailable regions are effectively excluded regions for this request if (documentClientRetryPolicy != null) { documentClientRetryPolicy.onBeforeSendRequest(request); } } } public void mergeContextInformationIntoDiagnosticsForPointRequest( RxDocumentServiceRequest request, PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker) { if (pointOperationContextForCircuitBreaker != null) { SerializationDiagnosticsContext serializationDiagnosticsContext = pointOperationContextForCircuitBreaker.getSerializationDiagnosticsContext(); diagnosticsAccessor.mergeSerializationDiagnosticContext(request.requestContext.cosmosDiagnostics, serializationDiagnosticsContext); } } public void addPartitionLevelUnavailableRegionsForFeedRequest( RxDocumentServiceRequest request, CosmosQueryRequestOptions options, CollectionRoutingMap collectionRoutingMap) { checkNotNull(collectionRoutingMap, "collectionRoutingMap cannot be null!"); PartitionKeyRange resolvedPartitionKeyRange = null; if (request.getPartitionKeyRangeIdentity() != null) { resolvedPartitionKeyRange = collectionRoutingMap.getRangeByPartitionKeyRangeId(request.getPartitionKeyRangeIdentity().getPartitionKeyRangeId()); } else if (request.getPartitionKeyInternal() != null) { String effectivePartitionKeyString = PartitionKeyInternalHelper.getEffectivePartitionKeyString(request.getPartitionKeyInternal(), ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor().getPartitionKeyDefinition(options)); resolvedPartitionKeyRange = collectionRoutingMap.getRangeByEffectivePartitionKey(effectivePartitionKeyString); } checkNotNull(resolvedPartitionKeyRange, "resolvedPartitionKeyRange cannot be null!"); // setting it here in case request.requestContext.resolvedPartitionKeyRange // is not assigned in either GlobalAddressResolver / RxGatewayStoreModel (possible if there are Gateway timeouts) // and circuit breaker also kicks in to mark a failure resolvedPartitionKeyRange (will result in NullPointerException and will // help failover as well) // also resolvedPartitionKeyRange will be overridden in GlobalAddressResolver / RxGatewayStoreModel irrespective // so staleness is not an issue (after doing a validation of parent-child relationship b/w initial and new partitionKeyRange) request.requestContext.resolvedPartitionKeyRange = resolvedPartitionKeyRange; if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(request)) { checkNotNull(globalPartitionEndpointManagerForCircuitBreaker, "globalPartitionEndpointManagerForCircuitBreaker cannot be null!"); List unavailableRegionsForPartition = this.globalPartitionEndpointManagerForCircuitBreaker.getUnavailableRegionsForPartitionKeyRange( request.getResourceId(), resolvedPartitionKeyRange, request.getOperationType()); request.requestContext.setUnavailableRegionsForPartition(unavailableRegionsForPartition); } } public void addPartitionLevelUnavailableRegionsForChangeFeedRequest( RxDocumentServiceRequest request, CosmosChangeFeedRequestOptions options, CollectionRoutingMap collectionRoutingMap) { checkNotNull(collectionRoutingMap, "collectionRoutingMap cannot be null!"); PartitionKeyRange resolvedPartitionKeyRange = null; if (request.getPartitionKeyRangeIdentity() != null) { resolvedPartitionKeyRange = collectionRoutingMap.getRangeByPartitionKeyRangeId(request.getPartitionKeyRangeIdentity().getPartitionKeyRangeId()); } else if (request.getPartitionKeyInternal() != null) { String effectivePartitionKeyString = PartitionKeyInternalHelper.getEffectivePartitionKeyString(request.getPartitionKeyInternal(), ImplementationBridgeHelpers.CosmosChangeFeedRequestOptionsHelper.getCosmosChangeFeedRequestOptionsAccessor().getPartitionKeyDefinition(options)); resolvedPartitionKeyRange = collectionRoutingMap.getRangeByEffectivePartitionKey(effectivePartitionKeyString); } checkNotNull(resolvedPartitionKeyRange, "resolvedPartitionKeyRange cannot be null!"); if (this.globalPartitionEndpointManagerForCircuitBreaker.isPartitionLevelCircuitBreakingApplicable(request)) { checkNotNull(globalPartitionEndpointManagerForCircuitBreaker, "globalPartitionEndpointManagerForCircuitBreaker cannot be null!"); List unavailableRegionsForPartition = this.globalPartitionEndpointManagerForCircuitBreaker.getUnavailableRegionsForPartitionKeyRange( request.getResourceId(), resolvedPartitionKeyRange, request.getOperationType()); request.requestContext.setUnavailableRegionsForPartition(unavailableRegionsForPartition); } } private Mono> wrapPointOperationWithAvailabilityStrategy( ResourceType resourceType, OperationType operationType, DocumentPointOperation callback, RequestOptions initialRequestOptions, boolean idempotentWriteRetriesEnabled, String collectionLink) { return wrapPointOperationWithAvailabilityStrategy( resourceType, operationType, callback, initialRequestOptions, idempotentWriteRetriesEnabled, this, collectionLink ); } private Mono> wrapPointOperationWithAvailabilityStrategy( ResourceType resourceType, OperationType operationType, DocumentPointOperation callback, RequestOptions initialRequestOptions, boolean idempotentWriteRetriesEnabled, DiagnosticsClientContext innerDiagnosticsFactory, String collectionLink) { checkNotNull(resourceType, "Argument 'resourceType' must not be null."); checkNotNull(operationType, "Argument 'operationType' must not be null."); checkNotNull(callback, "Argument 'callback' must not be null."); final RequestOptions nonNullRequestOptions = initialRequestOptions != null ? initialRequestOptions : new RequestOptions(); checkArgument( resourceType == ResourceType.Document, "This method can only be used for document point operations."); CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig = getEndToEndOperationLatencyPolicyConfig(nonNullRequestOptions, resourceType, operationType); List orderedApplicableRegionsForSpeculation = getApplicableRegionsForSpeculation( endToEndPolicyConfig, resourceType, operationType, idempotentWriteRetriesEnabled, nonNullRequestOptions); AtomicBoolean isOperationSuccessful = new AtomicBoolean(false); if (orderedApplicableRegionsForSpeculation.size() < 2) { // There is at most one applicable region - no hedging possible PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreakerForMainRequest = new PointOperationContextForCircuitBreaker( isOperationSuccessful, false, collectionLink, new SerializationDiagnosticsContext()); pointOperationContextForCircuitBreakerForMainRequest.setIsRequestHedged(false); return callback.apply(nonNullRequestOptions, endToEndPolicyConfig, innerDiagnosticsFactory, pointOperationContextForCircuitBreakerForMainRequest); } ThresholdBasedAvailabilityStrategy availabilityStrategy = (ThresholdBasedAvailabilityStrategy) endToEndPolicyConfig.getAvailabilityStrategy(); List> monoList = new ArrayList<>(); final ScopedDiagnosticsFactory diagnosticsFactory = new ScopedDiagnosticsFactory(innerDiagnosticsFactory, false); orderedApplicableRegionsForSpeculation .forEach(region -> { RequestOptions clonedOptions = new RequestOptions(nonNullRequestOptions); if (monoList.isEmpty()) { // no special error handling for transient errors to suppress them here // because any cross-regional retries are expected to be processed // by the ClientRetryPolicy for the initial request - so, any outcome of the // initial Mono should be treated as non-transient error - even when // the error would otherwise be treated as transient PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreakerForMainRequest = new PointOperationContextForCircuitBreaker( isOperationSuccessful, true, collectionLink, new SerializationDiagnosticsContext()); pointOperationContextForCircuitBreakerForMainRequest.setIsRequestHedged(false); Mono initialMonoAcrossAllRegions = callback.apply(clonedOptions, endToEndPolicyConfig, diagnosticsFactory, pointOperationContextForCircuitBreakerForMainRequest) .map(NonTransientPointOperationResult::new) .onErrorResume( RxDocumentClientImpl::isCosmosException, t -> Mono.just( new NonTransientPointOperationResult( Utils.as(Exceptions.unwrap(t), CosmosException.class)))); if (logger.isDebugEnabled()) { monoList.add(initialMonoAcrossAllRegions.doOnSubscribe(c -> logger.debug( "STARTING to process {} operation in region '{}'", operationType, region))); } else { monoList.add(initialMonoAcrossAllRegions); } } else { clonedOptions.setExcludedRegions( getEffectiveExcludedRegionsForHedging( nonNullRequestOptions.getExcludedRegions(), orderedApplicableRegionsForSpeculation, region) ); // Non-Transient errors are mapped to a value - this ensures the firstWithValue // operator below will complete the composite Mono for both successful values // and non-transient errors PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreakerForHedgedRequest = new PointOperationContextForCircuitBreaker( isOperationSuccessful, true, collectionLink, new SerializationDiagnosticsContext()); pointOperationContextForCircuitBreakerForHedgedRequest.setIsRequestHedged(true); Mono regionalCrossRegionRetryMono = callback.apply(clonedOptions, endToEndPolicyConfig, diagnosticsFactory, pointOperationContextForCircuitBreakerForHedgedRequest) .map(NonTransientPointOperationResult::new) .onErrorResume( RxDocumentClientImpl::isNonTransientCosmosException, t -> Mono.just( new NonTransientPointOperationResult( Utils.as(Exceptions.unwrap(t), CosmosException.class)))); Duration delayForCrossRegionalRetry = (availabilityStrategy) .getThreshold() .plus((availabilityStrategy) .getThresholdStep() .multipliedBy(monoList.size() - 1)); if (logger.isDebugEnabled()) { monoList.add( regionalCrossRegionRetryMono .doOnSubscribe(c -> logger.debug("STARTING to process {} operation in region '{}'", operationType, region)) .delaySubscription(delayForCrossRegionalRetry)); } else { monoList.add( regionalCrossRegionRetryMono .delaySubscription(delayForCrossRegionalRetry)); } } }); // NOTE - merging diagnosticsFactory cannot only happen in // doFinally operator because the doFinally operator is a side effect method - // meaning it executes concurrently with firing the onComplete/onError signal // doFinally is also triggered by cancellation // So, to make sure merging the Context happens synchronously in line we // have to ensure merging is happening on error/completion // and also in doOnCancel. return Mono .firstWithValue(monoList) .flatMap(nonTransientResult -> { diagnosticsFactory.merge(nonNullRequestOptions); if (nonTransientResult.isError()) { return Mono.error(nonTransientResult.exception); } return Mono.just(nonTransientResult.response); }) .onErrorMap(throwable -> { Throwable exception = Exceptions.unwrap(throwable); if (exception instanceof NoSuchElementException) { List innerThrowables = Exceptions .unwrapMultiple(exception.getCause()); int index = 0; for (Throwable innerThrowable : innerThrowables) { Throwable innerException = Exceptions.unwrap(innerThrowable); // collect latest CosmosException instance bubbling up for a region if (innerException instanceof CosmosException) { CosmosException cosmosException = Utils.as(innerException, CosmosException.class); diagnosticsFactory.merge(nonNullRequestOptions); return cosmosException; } else if (innerException instanceof NoSuchElementException) { logger.trace( "Operation in {} completed with empty result because it was cancelled.", orderedApplicableRegionsForSpeculation.get(index)); } else if (logger.isWarnEnabled()) { String message = "Unexpected Non-CosmosException when processing operation in '" + orderedApplicableRegionsForSpeculation.get(index) + "'."; logger.warn( message, innerException ); } index++; } } diagnosticsFactory.merge(nonNullRequestOptions); return exception; }) .doOnCancel(() -> diagnosticsFactory.merge(nonNullRequestOptions)); } private static boolean isCosmosException(Throwable t) { final Throwable unwrappedException = Exceptions.unwrap(t); return unwrappedException instanceof CosmosException; } private static boolean isNonTransientCosmosException(Throwable t) { final Throwable unwrappedException = Exceptions.unwrap(t); if (!(unwrappedException instanceof CosmosException)) { return false; } CosmosException cosmosException = Utils.as(unwrappedException, CosmosException.class); return isNonTransientResultForHedging( cosmosException.getStatusCode(), cosmosException.getSubStatusCode()); } private List getEffectiveExcludedRegionsForHedging( List initialExcludedRegions, List applicableRegions, String currentRegion) { // For hedging operations execution should only happen in the targeted region - no cross-regional // fail-overs should happen List effectiveExcludedRegions = new ArrayList<>(); if (initialExcludedRegions != null) { effectiveExcludedRegions.addAll(initialExcludedRegions); } for (String applicableRegion: applicableRegions) { if (!applicableRegion.equals(currentRegion)) { effectiveExcludedRegions.add(applicableRegion); } } return effectiveExcludedRegions; } private static boolean isNonTransientResultForHedging(int statusCode, int subStatusCode) { // All 1xx, 2xx and 3xx status codes should be treated as final result if (statusCode < HttpConstants.StatusCodes.BADREQUEST) { return true; } // Treat OperationCancelledException as non-transient timeout if (statusCode == HttpConstants.StatusCodes.REQUEST_TIMEOUT && subStatusCode == HttpConstants.SubStatusCodes.CLIENT_OPERATION_TIMEOUT) { return true; } // Status codes below indicate non-transient errors if (statusCode == HttpConstants.StatusCodes.BADREQUEST || statusCode == HttpConstants.StatusCodes.CONFLICT || statusCode == HttpConstants.StatusCodes.METHOD_NOT_ALLOWED || statusCode == HttpConstants.StatusCodes.PRECONDITION_FAILED || statusCode == HttpConstants.StatusCodes.REQUEST_ENTITY_TOO_LARGE || statusCode == HttpConstants.StatusCodes.UNAUTHORIZED) { return true; } // 404 - NotFound is also a final result - it means document was not yet available // after enforcing whatever the consistency model is if (statusCode == HttpConstants.StatusCodes.NOTFOUND && subStatusCode == HttpConstants.SubStatusCodes.UNKNOWN) { return true; } // All other errors should be treated as possibly transient return false; } private DiagnosticsClientContext getEffectiveClientContext(DiagnosticsClientContext clientContextOverride) { if (clientContextOverride != null) { return clientContextOverride; } return this; } /** * Returns the applicable endpoints ordered by preference list if any * @param operationType - the operationT * @return the applicable endpoints ordered by preference list if any */ private List getApplicableEndPoints(OperationType operationType, List excludedRegions) { if (operationType.isReadOnlyOperation()) { return withoutNulls(this.globalEndpointManager.getApplicableReadEndpoints(excludedRegions)); } else if (operationType.isWriteOperation()) { return withoutNulls(this.globalEndpointManager.getApplicableWriteEndpoints(excludedRegions)); } return EMPTY_ENDPOINT_LIST; } private static List withoutNulls(List orderedEffectiveEndpointsList) { if (orderedEffectiveEndpointsList == null) { return EMPTY_ENDPOINT_LIST; } int i = 0; while (i < orderedEffectiveEndpointsList.size()) { if (orderedEffectiveEndpointsList.get(i) == null) { orderedEffectiveEndpointsList.remove(i); } else { i++; } } return orderedEffectiveEndpointsList; } private List getApplicableRegionsForSpeculation( CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig, ResourceType resourceType, OperationType operationType, boolean isIdempotentWriteRetriesEnabled, RequestOptions options) { return getApplicableRegionsForSpeculation( endToEndPolicyConfig, resourceType, operationType, isIdempotentWriteRetriesEnabled, options.getExcludedRegions()); } private List getApplicableRegionsForSpeculation( CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig, ResourceType resourceType, OperationType operationType, boolean isIdempotentWriteRetriesEnabled, List excludedRegions) { if (endToEndPolicyConfig == null || !endToEndPolicyConfig.isEnabled()) { return EMPTY_REGION_LIST; } if (resourceType != ResourceType.Document) { return EMPTY_REGION_LIST; } if (operationType.isWriteOperation() && !isIdempotentWriteRetriesEnabled) { return EMPTY_REGION_LIST; } if (operationType.isWriteOperation() && !this.globalEndpointManager.canUseMultipleWriteLocations()) { return EMPTY_REGION_LIST; } if (!(endToEndPolicyConfig.getAvailabilityStrategy() instanceof ThresholdBasedAvailabilityStrategy)) { return EMPTY_REGION_LIST; } List endpoints = getApplicableEndPoints(operationType, excludedRegions); HashSet normalizedExcludedRegions = new HashSet<>(); if (excludedRegions != null) { excludedRegions.forEach(r -> normalizedExcludedRegions.add(r.toLowerCase(Locale.ROOT))); } List orderedRegionsForSpeculation = new ArrayList<>(); endpoints.forEach(uri -> { String regionName = this.globalEndpointManager.getRegionName(uri, operationType); if (!normalizedExcludedRegions.contains(regionName.toLowerCase(Locale.ROOT))) { orderedRegionsForSpeculation.add(regionName); } }); return orderedRegionsForSpeculation; } private Mono executeFeedOperationWithAvailabilityStrategy( final ResourceType resourceType, final OperationType operationType, final Supplier retryPolicyFactory, final RxDocumentServiceRequest req, final BiFunction, RxDocumentServiceRequest, Mono> feedOperation, final String collectionLink) { checkNotNull(retryPolicyFactory, "Argument 'retryPolicyFactory' must not be null."); checkNotNull(req, "Argument 'req' must not be null."); assert(resourceType == ResourceType.Document); CosmosEndToEndOperationLatencyPolicyConfig endToEndPolicyConfig = this.getEffectiveEndToEndOperationLatencyPolicyConfig( req.requestContext.getEndToEndOperationLatencyPolicyConfig(), resourceType, operationType); List initialExcludedRegions = req.requestContext.getExcludeRegions(); List orderedApplicableRegionsForSpeculation = this.getApplicableRegionsForSpeculation( endToEndPolicyConfig, resourceType, operationType, false, initialExcludedRegions); Map partitionKeyRangesWithSuccess = new ConcurrentHashMap<>(); if (orderedApplicableRegionsForSpeculation.size() < 2) { FeedOperationContextForCircuitBreaker feedOperationContextForCircuitBreakerForRequestOutsideOfAvailabilityStrategyFlow = new FeedOperationContextForCircuitBreaker( partitionKeyRangesWithSuccess, false, collectionLink); feedOperationContextForCircuitBreakerForRequestOutsideOfAvailabilityStrategyFlow.setIsRequestHedged(false); req.requestContext.setFeedOperationContext(feedOperationContextForCircuitBreakerForRequestOutsideOfAvailabilityStrategyFlow); // There is at most one applicable region - no hedging possible return feedOperation.apply(retryPolicyFactory, req); } FeedOperationContextForCircuitBreaker feedOperationContextForCircuitBreakerForParentRequestInAvailabilityStrategyFlow = new FeedOperationContextForCircuitBreaker( partitionKeyRangesWithSuccess, true, collectionLink); feedOperationContextForCircuitBreakerForParentRequestInAvailabilityStrategyFlow.setIsRequestHedged(false); req.requestContext.setFeedOperationContext(feedOperationContextForCircuitBreakerForParentRequestInAvailabilityStrategyFlow); ThresholdBasedAvailabilityStrategy availabilityStrategy = (ThresholdBasedAvailabilityStrategy)endToEndPolicyConfig.getAvailabilityStrategy(); List>> monoList = new ArrayList<>(); orderedApplicableRegionsForSpeculation .forEach(region -> { RxDocumentServiceRequest clonedRequest = req.clone(); if (monoList.isEmpty()) { // no special error handling for transient errors to suppress them here // because any cross-regional retries are expected to be processed // by the ClientRetryPolicy for the initial request - so, any outcome of the // initial Mono should be treated as non-transient error - even when // the error would otherwise be treated as transient FeedOperationContextForCircuitBreaker feedOperationContextForCircuitBreakerForNonHedgedRequest = new FeedOperationContextForCircuitBreaker( partitionKeyRangesWithSuccess, true, collectionLink); feedOperationContextForCircuitBreakerForNonHedgedRequest.setIsRequestHedged(false); clonedRequest.requestContext.setFeedOperationContext(feedOperationContextForCircuitBreakerForNonHedgedRequest); Mono> initialMonoAcrossAllRegions = handleCircuitBreakingFeedbackForFeedOperationWithAvailabilityStrategy(feedOperation.apply(retryPolicyFactory, clonedRequest) .map(NonTransientFeedOperationResult::new) .onErrorResume( RxDocumentClientImpl::isCosmosException, t -> Mono.just( new NonTransientFeedOperationResult<>( Utils.as(Exceptions.unwrap(t), CosmosException.class)))), clonedRequest); if (logger.isDebugEnabled()) { monoList.add(initialMonoAcrossAllRegions.doOnSubscribe(c -> logger.debug( "STARTING to process {} operation in region '{}'", operationType, region))); } else { monoList.add(initialMonoAcrossAllRegions); } } else { clonedRequest.requestContext.setExcludeRegions( getEffectiveExcludedRegionsForHedging( initialExcludedRegions, orderedApplicableRegionsForSpeculation, region) ); FeedOperationContextForCircuitBreaker feedOperationContextForCircuitBreakerForHedgedRequest = new FeedOperationContextForCircuitBreaker( partitionKeyRangesWithSuccess, true, collectionLink); feedOperationContextForCircuitBreakerForHedgedRequest.setIsRequestHedged(true); clonedRequest.requestContext.setFeedOperationContext(feedOperationContextForCircuitBreakerForHedgedRequest); clonedRequest.requestContext.setKeywordIdentifiers(req.requestContext.getKeywordIdentifiers()); // Non-Transient errors are mapped to a value - this ensures the firstWithValue // operator below will complete the composite Mono for both successful values // and non-transient errors Mono> regionalCrossRegionRetryMono = handleCircuitBreakingFeedbackForFeedOperationWithAvailabilityStrategy(feedOperation.apply(retryPolicyFactory, clonedRequest) .map(NonTransientFeedOperationResult::new) .onErrorResume( RxDocumentClientImpl::isNonTransientCosmosException, t -> Mono.just( new NonTransientFeedOperationResult<>( Utils.as(Exceptions.unwrap(t), CosmosException.class)))), clonedRequest); Duration delayForCrossRegionalRetry = (availabilityStrategy) .getThreshold() .plus((availabilityStrategy) .getThresholdStep() .multipliedBy(monoList.size() - 1)); if (logger.isDebugEnabled()) { monoList.add( regionalCrossRegionRetryMono .doOnSubscribe(c -> logger.debug("STARTING to process {} operation in region '{}'", operationType, region)) .delaySubscription(delayForCrossRegionalRetry)); } else { monoList.add( regionalCrossRegionRetryMono .delaySubscription(delayForCrossRegionalRetry)); } } }); // NOTE - merging diagnosticsFactory cannot only happen in // doFinally operator because the doFinally operator is a side effect method - // meaning it executes concurrently with firing the onComplete/onError signal // doFinally is also triggered by cancellation // So, to make sure merging the Context happens synchronously in line we // have to ensure merging is happening on error/completion // and also in doOnCancel. return Mono .firstWithValue(monoList) .flatMap(nonTransientResult -> { if (nonTransientResult.isError()) { return Mono.error(nonTransientResult.exception); } return Mono.just(nonTransientResult.response); }) .onErrorMap(throwable -> { Throwable exception = Exceptions.unwrap(throwable); if (exception instanceof NoSuchElementException) { List innerThrowables = Exceptions .unwrapMultiple(exception.getCause()); int index = 0; for (Throwable innerThrowable : innerThrowables) { Throwable innerException = Exceptions.unwrap(innerThrowable); // collect latest CosmosException instance bubbling up for a region if (innerException instanceof CosmosException) { return Utils.as(innerException, CosmosException.class); } else if (innerException instanceof NoSuchElementException) { logger.trace( "Operation in {} completed with empty result because it was cancelled.", orderedApplicableRegionsForSpeculation.get(index)); } else if (logger.isWarnEnabled()) { String message = "Unexpected Non-CosmosException when processing operation in '" + orderedApplicableRegionsForSpeculation.get(index) + "'."; logger.warn( message, innerException ); } index++; } } return exception; }); } private void handleLocationCancellationExceptionForPartitionKeyRange(RxDocumentServiceRequest failedRequest) { URI firstContactedLocationEndpoint = diagnosticsAccessor .getFirstContactedLocationEndpoint(failedRequest.requestContext.cosmosDiagnostics); if (firstContactedLocationEndpoint != null) { this.globalPartitionEndpointManagerForCircuitBreaker .handleLocationExceptionForPartitionKeyRange(failedRequest, firstContactedLocationEndpoint); } } @FunctionalInterface private interface DocumentPointOperation { Mono> apply( RequestOptions requestOptions, CosmosEndToEndOperationLatencyPolicyConfig endToEndOperationLatencyPolicyConfig, DiagnosticsClientContext clientContextOverride, PointOperationContextForCircuitBreaker pointOperationContextForCircuitBreaker); } private static class NonTransientPointOperationResult { private final ResourceResponse response; private final CosmosException exception; public NonTransientPointOperationResult(CosmosException exception) { checkNotNull(exception, "Argument 'exception' must not be null."); this.exception = exception; this.response = null; } public NonTransientPointOperationResult(ResourceResponse response) { checkNotNull(response, "Argument 'response' must not be null."); this.exception = null; this.response = response; } public boolean isError() { return this.exception != null; } public CosmosException getException() { return this.exception; } public ResourceResponse getResponse() { return this.response; } } private static class NonTransientFeedOperationResult { private final T response; private final CosmosException exception; public NonTransientFeedOperationResult(CosmosException exception) { checkNotNull(exception, "Argument 'exception' must not be null."); this.exception = exception; this.response = null; } public NonTransientFeedOperationResult(T response) { checkNotNull(response, "Argument 'response' must not be null."); this.exception = null; this.response = response; } public boolean isError() { return this.exception != null; } public CosmosException getException() { return this.exception; } public T getResponse() { return this.response; } } private static class ScopedDiagnosticsFactory implements DiagnosticsClientContext { private final AtomicBoolean isMerged = new AtomicBoolean(false); private final DiagnosticsClientContext inner; private final ConcurrentLinkedQueue createdDiagnostics; private final boolean shouldCaptureAllFeedDiagnostics; private final AtomicReference mostRecentlyCreatedDiagnostics = new AtomicReference<>(null); public ScopedDiagnosticsFactory(DiagnosticsClientContext inner, boolean shouldCaptureAllFeedDiagnostics) { checkNotNull(inner, "Argument 'inner' must not be null."); this.inner = inner; this.createdDiagnostics = new ConcurrentLinkedQueue<>(); this.shouldCaptureAllFeedDiagnostics = shouldCaptureAllFeedDiagnostics; } @Override public DiagnosticsClientConfig getConfig() { return inner.getConfig(); } @Override public CosmosDiagnostics createDiagnostics() { CosmosDiagnostics diagnostics = inner.createDiagnostics(); createdDiagnostics.add(diagnostics); mostRecentlyCreatedDiagnostics.set(diagnostics); return diagnostics; } @Override public String getUserAgent() { return inner.getUserAgent(); } @Override public CosmosDiagnostics getMostRecentlyCreatedDiagnostics() { return this.mostRecentlyCreatedDiagnostics.get(); } public void merge(RequestOptions requestOptions) { CosmosDiagnosticsContext knownCtx = null; if (requestOptions != null) { CosmosDiagnosticsContext ctxSnapshot = requestOptions.getDiagnosticsContextSnapshot(); if (ctxSnapshot != null) { knownCtx = requestOptions.getDiagnosticsContextSnapshot(); } } merge(knownCtx); } public void merge(CosmosDiagnosticsContext knownCtx) { if (!isMerged.compareAndSet(false, true)) { return; } CosmosDiagnosticsContext ctx = null; if (knownCtx != null) { ctx = knownCtx; } else { for (CosmosDiagnostics diagnostics : this.createdDiagnostics) { if (diagnostics.getDiagnosticsContext() != null) { ctx = diagnostics.getDiagnosticsContext(); break; } } } if (ctx == null) { return; } for (CosmosDiagnostics diagnostics : this.createdDiagnostics) { if (diagnostics.getDiagnosticsContext() == null && diagnosticsAccessor.isNotEmpty(diagnostics)) { if (this.shouldCaptureAllFeedDiagnostics && diagnosticsAccessor.getFeedResponseDiagnostics(diagnostics) != null) { AtomicBoolean isCaptured = diagnosticsAccessor.isDiagnosticsCapturedInPagedFlux(diagnostics); if (isCaptured != null) { // Diagnostics captured in the ScopedDiagnosticsFactory should always be kept isCaptured.set(true); } } ctxAccessor.addDiagnostics(ctx, diagnostics); } } } public void reset() { this.createdDiagnostics.clear(); this.isMerged.set(false); } } }