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

com.azure.cosmos.encryption.CosmosEncryptionAsyncContainer Maven / Gradle / Ivy

The newest version!
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.cosmos.encryption;

import com.azure.cosmos.CosmosAsyncContainer;
import com.azure.cosmos.CosmosBridgeInternal;
import com.azure.cosmos.CosmosException;
import com.azure.cosmos.CosmosItemSerializer;
import com.azure.cosmos.encryption.implementation.Constants;
import com.azure.cosmos.encryption.implementation.CosmosEncryptionQueryTransformer;
import com.azure.cosmos.encryption.implementation.CosmosResponseFactory;
import com.azure.cosmos.encryption.implementation.EncryptionImplementationBridgeHelpers;
import com.azure.cosmos.encryption.implementation.EncryptionProcessor;
import com.azure.cosmos.encryption.implementation.EncryptionSettings;
import com.azure.cosmos.encryption.implementation.EncryptionUtils;
import com.azure.cosmos.encryption.implementation.mdesrc.cryptography.MicrosoftDataEncryptionException;
import com.azure.cosmos.encryption.models.SqlQuerySpecWithEncryption;
import com.azure.cosmos.implementation.CosmosBulkExecutionOptionsImpl;
import com.azure.cosmos.implementation.CosmosPagedFluxOptions;
import com.azure.cosmos.implementation.HttpConstants;
import com.azure.cosmos.implementation.ImplementationBridgeHelpers;
import com.azure.cosmos.implementation.JsonSerializable;
import com.azure.cosmos.implementation.ObjectNodeMap;
import com.azure.cosmos.implementation.Strings;
import com.azure.cosmos.implementation.apachecommons.lang.tuple.Pair;
import com.azure.cosmos.implementation.batch.ItemBatchOperation;
import com.azure.cosmos.implementation.batch.ItemBulkOperation;
import com.azure.cosmos.implementation.guava25.base.Preconditions;
import com.azure.cosmos.implementation.patch.PatchOperation;
import com.azure.cosmos.implementation.patch.PatchOperationCore;
import com.azure.cosmos.implementation.patch.PatchOperationType;
import com.azure.cosmos.implementation.query.Transformer;
import com.azure.cosmos.models.CosmosBatch;
import com.azure.cosmos.models.CosmosBatchOperationResult;
import com.azure.cosmos.models.CosmosBatchRequestOptions;
import com.azure.cosmos.models.CosmosBatchResponse;
import com.azure.cosmos.models.CosmosBulkExecutionOptions;
import com.azure.cosmos.models.CosmosBulkItemResponse;
import com.azure.cosmos.models.CosmosBulkOperationResponse;
import com.azure.cosmos.models.CosmosChangeFeedRequestOptions;
import com.azure.cosmos.models.CosmosItemOperation;
import com.azure.cosmos.models.CosmosItemRequestOptions;
import com.azure.cosmos.models.CosmosItemResponse;
import com.azure.cosmos.models.CosmosPatchItemRequestOptions;
import com.azure.cosmos.models.CosmosPatchOperations;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.FeedResponse;
import com.azure.cosmos.models.ModelBridgeInternal;
import com.azure.cosmos.models.PartitionKey;
import com.azure.cosmos.models.PartitionKeyBuilder;
import com.azure.cosmos.models.SqlParameter;
import com.azure.cosmos.models.SqlQuerySpec;
import com.azure.cosmos.util.CosmosPagedFlux;
import com.azure.cosmos.util.UtilBridgeInternal;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

import static com.azure.cosmos.implementation.Utils.getEffectiveCosmosChangeFeedRequestOptions;
import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull;

/**
 * CosmosAsyncContainer with encryption capabilities.
 */
public final class CosmosEncryptionAsyncContainer {
    private final Scheduler encryptionScheduler;
    private final CosmosResponseFactory responseFactory = new CosmosResponseFactory();
    private final CosmosAsyncContainer container;
    private final EncryptionProcessor encryptionProcessor;

    private final CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient;

    private final static ImplementationBridgeHelpers.CosmosItemSerializerHelper.CosmosItemSerializerAccessor itemSerializerAccessor =
        ImplementationBridgeHelpers.CosmosItemSerializerHelper.getCosmosItemSerializerAccessor();
    private final static ImplementationBridgeHelpers.CosmosItemResponseHelper.CosmosItemResponseBuilderAccessor cosmosItemResponseBuilderAccessor = ImplementationBridgeHelpers.CosmosItemResponseHelper.getCosmosItemResponseBuilderAccessor();
    private final static ImplementationBridgeHelpers.CosmosItemRequestOptionsHelper.CosmosItemRequestOptionsAccessor cosmosItemRequestOptionsAccessor = ImplementationBridgeHelpers.CosmosItemRequestOptionsHelper.getCosmosItemRequestOptionsAccessor();
    private final static ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.CosmosQueryRequestOptionsAccessor cosmosQueryRequestOptionsAccessor = ImplementationBridgeHelpers.CosmosQueryRequestOptionsHelper.getCosmosQueryRequestOptionsAccessor();
    private final static ImplementationBridgeHelpers.CosmosChangeFeedRequestOptionsHelper.CosmosChangeFeedRequestOptionsAccessor cosmosChangeFeedRequestOptionsAccessor = ImplementationBridgeHelpers.CosmosChangeFeedRequestOptionsHelper.getCosmosChangeFeedRequestOptionsAccessor();
    private final static ImplementationBridgeHelpers.CosmosAsyncContainerHelper.CosmosAsyncContainerAccessor cosmosAsyncContainerAccessor = ImplementationBridgeHelpers.CosmosAsyncContainerHelper.getCosmosAsyncContainerAccessor();
    private final static ImplementationBridgeHelpers.CosmosBatchHelper.CosmosBatchAccessor cosmosBatchAccessor = ImplementationBridgeHelpers.CosmosBatchHelper.getCosmosBatchAccessor();
    private final static ImplementationBridgeHelpers.CosmosBatchResponseHelper.CosmosBatchResponseAccessor cosmosBatchResponseAccessor = ImplementationBridgeHelpers.CosmosBatchResponseHelper.getCosmosBatchResponseAccessor();
    private final static ImplementationBridgeHelpers.CosmosBatchOperationResultHelper.CosmosBatchOperationResultAccessor cosmosBatchOperationResultAccessor = ImplementationBridgeHelpers.CosmosBatchOperationResultHelper.getCosmosBatchOperationResultAccessor();
    private final static ImplementationBridgeHelpers.CosmosBatchRequestOptionsHelper.CosmosBatchRequestOptionsAccessor cosmosBatchRequestOptionsAccessor = ImplementationBridgeHelpers.CosmosBatchRequestOptionsHelper.getCosmosBatchRequestOptionsAccessor();
    private final static ImplementationBridgeHelpers.CosmosPatchOperationsHelper.CosmosPatchOperationsAccessor cosmosPatchOperationsAccessor = ImplementationBridgeHelpers.CosmosPatchOperationsHelper.getCosmosPatchOperationsAccessor();
    private final static ImplementationBridgeHelpers.CosmosBulkExecutionOptionsHelper.CosmosBulkExecutionOptionsAccessor cosmosBulkExecutionOptionsAccessor = ImplementationBridgeHelpers.CosmosBulkExecutionOptionsHelper.getCosmosBulkExecutionOptionsAccessor();
    private final static ImplementationBridgeHelpers.CosmosBulkItemResponseHelper.CosmosBulkItemResponseAccessor cosmosBulkItemResponseAccessor = ImplementationBridgeHelpers.CosmosBulkItemResponseHelper.getCosmosBulkItemResponseAccessor();
    private final static EncryptionImplementationBridgeHelpers.SqlQuerySpecWithEncryptionHelper.SqlQuerySpecWithEncryptionAccessor specWithEncryptionAccessor = EncryptionImplementationBridgeHelpers.SqlQuerySpecWithEncryptionHelper.getSqlQuerySpecWithEncryptionAccessor();

    CosmosEncryptionAsyncContainer(CosmosAsyncContainer container,
                                   CosmosEncryptionAsyncClient cosmosEncryptionAsyncClient) {
        this.container = container;
        this.cosmosEncryptionAsyncClient = cosmosEncryptionAsyncClient;
        this.encryptionProcessor = new EncryptionProcessor(this.container, cosmosEncryptionAsyncClient);
        this.encryptionScheduler = Schedulers.parallel();
    }

    EncryptionProcessor getEncryptionProcessor() {
        return this.encryptionProcessor;
    }

    /**
     * Creates an item.
     * 

* After subscription the operation will be performed. The {@link Mono} upon * successful completion will contain a single resource response with the * created Cosmos item. In case of failure the {@link Mono} will error. * * @param the type parameter. * @param item the Cosmos item represented as a POJO or Cosmos item object. * @return an {@link Mono} containing the single resource response with the * created Cosmos item or an error. */ @SuppressWarnings("unchecked") public Mono> createItem(T item) { return createItem(item, new CosmosItemRequestOptions()); } /** * Creates a Cosmos item. * * @param the type parameter. * @param item the item. * @param requestOptions the item request options. * @return an {@link Mono} containing the single resource response with the created Cosmos item or an error. */ @SuppressWarnings("unchecked") public Mono> createItem(T item, CosmosItemRequestOptions requestOptions) { Preconditions.checkNotNull(item, "item"); if (requestOptions == null) { requestOptions = new CosmosItemRequestOptions(); } byte[] streamPayload = cosmosSerializerToStream(item, getEffectiveItemSerializer(requestOptions)); return createItemHelper(streamPayload, requestOptions,(Class) item.getClass(), false ); } /** * Creates an item. *

* After subscription the operation will be performed. The {@link Mono} upon * successful completion will contain a single resource response with the * created Cosmos item. In case of failure the {@link Mono} will error. * * @param the type parameter. * @param item the Cosmos item represented as a POJO or Cosmos item object. * @param partitionKey the partition key. * @param requestOptions the request options. * @return an {@link Mono} containing the single resource response with the created Cosmos item or an error. */ @SuppressWarnings("unchecked") public Mono> createItem(T item, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) { Preconditions.checkNotNull(item, "item"); if (requestOptions == null) { requestOptions = new CosmosItemRequestOptions(); } Preconditions.checkArgument(partitionKey != null, "partitionKey cannot be null for operations using " + "EncryptionContainer."); byte[] streamPayload = cosmosSerializerToStream(item, getEffectiveItemSerializer(requestOptions)); return createItemHelper(streamPayload, partitionKey, requestOptions, (Class) item.getClass(), false); } /** * Deletes an item. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a single Cosmos item response for the deleted item. * * @param itemId the item id. * @param partitionKey the partition key. * @return an {@link Mono} containing the Cosmos item resource response. */ public Mono> deleteItem(String itemId, PartitionKey partitionKey) { return deleteItem(itemId, partitionKey, new CosmosItemRequestOptions()); } /** * Deletes the item. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a single Cosmos item response for the deleted item. * * @param itemId id of the item. * @param partitionKey partitionKey of the item. * @param requestOptions the request options. * @return an {@link Mono} containing the Cosmos item resource response. */ public Mono> deleteItem(String itemId, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) { return deleteItemInternal(itemId, partitionKey, requestOptions); } private Mono> deleteItemInternal(String itemId, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) { this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync(); return Mono.just(this.encryptionProcessor.getEncryptionSettings()) .flatMap(settings -> { try { return Mono.zip( checkAndGetEncryptedId(itemId, settings), checkAndGetEncryptedPartitionKey(partitionKey, settings) ).flatMap(encryptedIdPartitionTuple -> container.deleteItem(encryptedIdPartitionTuple.getT1(), encryptedIdPartitionTuple.getT2(), requestOptions)); } catch (Exception ex) { return Mono.error(ex); } }); } private Mono checkAndGetEncryptedId(String itemId, EncryptionSettings encryptionSettings) { if (this.encryptionProcessor.getClientEncryptionPolicy().getIncludedPaths().stream(). anyMatch(includedPath -> includedPath.getPath().substring(1).equals(Constants.PROPERTY_NAME_ID))) { return this.getEncryptedItem(encryptionSettings, Constants.PROPERTY_NAME_ID, itemId); } return Mono.just(itemId); } private Mono checkAndGetEncryptedPartitionKey(PartitionKey partitionKey, EncryptionSettings encryptionSettings) { if (encryptionSettings.getPartitionKeyPaths().isEmpty()) { return Mono.just(partitionKey); } JsonNode partitionKeyNode; try { partitionKeyNode = EncryptionUtils.getSimpleObjectMapper().readTree(partitionKey.toString()); } catch (JsonProcessingException ex) { return Mono.error(ex); } if (partitionKeyNode.isArray() && partitionKeyNode.size() > 1) { ArrayNode arrayNode = (ArrayNode) partitionKeyNode; return Mono.just(new PartitionKeyBuilder()) .flatMap(partitionKeyBuilder -> Flux.fromIterable(encryptionSettings.getPartitionKeyPaths()) .flatMap(path -> { // case: partition key path is /a/b/c and the client encryption policy has /a in path. // hence encrypt the partition key value with using its top level path /a since // /c would have been encrypted in the document using /a's policy. String partitionKeyPath = path.split("/")[1]; String childPartitionKey = arrayNode.elements().next().textValue(); if (this.encryptionProcessor.getClientEncryptionPolicy().getIncludedPaths().stream(). anyMatch(includedPath -> includedPath.getPath().substring(1).equals(partitionKeyPath))) { partitionKeyBuilder.add(childPartitionKey); return Mono.empty(); } return getEncryptedItem(encryptionSettings, partitionKeyPath, childPartitionKey); }) .collectList() .flatMapMany(Flux::fromIterable) .doOnNext(partitionKeyBuilder::add) .then(Mono.just(partitionKeyBuilder.build()))); } else { return Mono.just(encryptionSettings.getPartitionKeyPaths().get(0)) .flatMap(path -> { String partitionKeyPath = path.split("/")[1]; if (this.encryptionProcessor.getClientEncryptionPolicy().getIncludedPaths().stream(). noneMatch(includedPath -> includedPath.getPath().substring(1).equals(partitionKeyPath))) { return Mono.just(partitionKeyNode.elements().next().textValue()); } return getEncryptedItem(encryptionSettings, partitionKeyPath, partitionKeyNode.elements().next().textValue()); }) .flatMap(encryptedPartitionKey -> Mono.just(new PartitionKey(encryptedPartitionKey))); } } private Mono getEncryptedItem(EncryptionSettings encryptionSettings, String propertyName, String propertyValue) { return encryptionSettings .getEncryptionSettingForPropertyAsync(propertyName, this.encryptionProcessor) .flatMap(settings -> { try { return Mono.just( this.encryptionProcessor.encryptAndSerializeValue(settings, propertyValue, propertyName)); } catch (MicrosoftDataEncryptionException ex) { return Mono.error(ex); } }); } /** * Deletes the item. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a single Cosmos item response for the deleted item. * * @param the type parameter. * @param item item to be deleted. * @param requestOptions the request options. * @return an {@link Mono} containing the Cosmos item resource response. */ public Mono> deleteItem(T item, CosmosItemRequestOptions requestOptions) { return container.deleteItem(item, requestOptions); } /** * Deletes all items in the Container with the specified partitionKey value. * Starts an asynchronous Cosmos DB background operation which deletes all items in the Container with the specified value. * The asynchronous Cosmos DB background operation runs using a percentage of user RUs. * After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a single Cosmos item response for all the deleted items. * * @param partitionKey partitionKey of the item. * @param requestOptions the request options. * @return an {@link Mono} containing the Cosmos item resource response. */ // TODO Make this api public once it is GA in cosmos core library Mono> deleteAllItemsByPartitionKey(PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) { final CosmosItemRequestOptions options = Optional.ofNullable(requestOptions) .orElse(new CosmosItemRequestOptions()); return deleteAllItemsByPartitionKeyInternal(partitionKey, options); } private Mono> deleteAllItemsByPartitionKeyInternal(PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) { return this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptedSettings -> checkAndGetEncryptedPartitionKey(partitionKey, encryptedSettings)) .flatMap(encryptedPartitionKey -> container.deleteAllItemsByPartitionKey(encryptedPartitionKey, requestOptions)); } /** * Upserts an item. *

* After subscription the operation will be performed. The {@link Mono} upon * successful completion will contain a single resource response with the * upserted item. In case of failure the {@link Mono} will error. * * @param the type parameter. * @param item the item represented as a POJO or Item object to upsert. * @return an {@link Mono} containing the single resource response with the upserted item or an error. */ @SuppressWarnings("unchecked") public Mono> upsertItem(T item) { return upsertItem(item, new CosmosItemRequestOptions()); } /** * Upserts an item. *

* After subscription the operation will be performed. The {@link Mono} upon * successful completion will contain a single resource response with the * upserted item. In case of failure the {@link Mono} will error. * * @param the type parameter. * @param item the item represented as a POJO or Item object to upsert. * @param requestOptions the request options. * @return an {@link Mono} containing the single resource response with the upserted item or an error. */ @SuppressWarnings("unchecked") public Mono> upsertItem(T item, CosmosItemRequestOptions requestOptions) { Preconditions.checkNotNull(item, "item"); if (requestOptions == null) { requestOptions = new CosmosItemRequestOptions(); } byte[] streamPayload = cosmosSerializerToStream(item, getEffectiveItemSerializer(requestOptions)); return upsertItemHelper(streamPayload, requestOptions, (Class) item.getClass(), false); } /** * Upserts an item. *

* After subscription the operation will be performed. The {@link Mono} upon * successful completion will contain a single resource response with the * upserted item. In case of failure the {@link Mono} will error. * * @param the type parameter. * @param item the item represented as a POJO or Item object to upsert. * @param partitionKey the partition key. * @param requestOptions the request options. * @return an {@link Mono} containing the single resource response with the upserted item or an error. */ @SuppressWarnings("unchecked") public Mono> upsertItem(T item, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) { Preconditions.checkNotNull(item, "item"); if (requestOptions == null) { requestOptions = new CosmosItemRequestOptions(); } Preconditions.checkArgument(partitionKey != null, "partitionKey cannot be null for operations using " + "EncryptionContainer."); byte[] streamPayload = cosmosSerializerToStream(item, getEffectiveItemSerializer(requestOptions)); return upsertItemHelper(streamPayload, partitionKey, requestOptions, (Class) item.getClass(), false); } /** * Replaces an item with the passed in item and encrypts the requested fields. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a single Cosmos item response with the replaced item. * * @param the type parameter. * @param item the item to replace (containing the item id). * @param itemId the item id. * @param partitionKey the partition key. * @return an {@link Mono} containing the Cosmos item resource response with the replaced item or an error. */ public Mono> replaceItem(T item, String itemId, PartitionKey partitionKey) { return replaceItem(item, itemId, partitionKey, new CosmosItemRequestOptions()); } /** * Replaces an item with the passed in item and encrypts the requested fields. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a single Cosmos item response with the replaced item. * * @param the type parameter. * @param item the item to replace (containing the item id). * @param itemId the item id. * @param partitionKey the partition key. * @param requestOptions the request comosItemRequestOptions. * @return an {@link Mono} containing the Cosmos item resource response with the replaced item or an error. */ @SuppressWarnings("unchecked") public Mono> replaceItem(T item, String itemId, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions) { Preconditions.checkNotNull(item, "item"); if (requestOptions == null) { requestOptions = new CosmosItemRequestOptions(); } Preconditions.checkArgument(partitionKey != null, "partitionKey cannot be null for operations using " + "EncryptionContainer."); byte[] streamPayload = cosmosSerializerToStream(item, getEffectiveItemSerializer(requestOptions)); return replaceItemHelper(streamPayload, itemId, partitionKey, requestOptions, (Class) item.getClass(), false); } /** * Reads an item. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain an item response with the read item. * * @param the type parameter. * @param id the item id. * @param partitionKey the partition key. * @param classType the item type. * @return an {@link Mono} containing the Cosmos item response with the read item or an error. */ public Mono> readItem(String id, PartitionKey partitionKey, Class classType) { return readItem(id, partitionKey, ModelBridgeInternal.createCosmosItemRequestOptions(partitionKey), classType); } /** * Reads an item using a configured {@link CosmosItemRequestOptions}. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a Cosmos item response with the read item. * * @param the type parameter. * @param id the item id. * @param partitionKey the partition key. * @param requestOptions the request {@link CosmosItemRequestOptions}. * @param classType the item type. * @return an {@link Mono} containing the Cosmos item response with the read item or an error. */ public Mono> readItem(String id, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions, Class classType) { final CosmosItemRequestOptions options = Optional.ofNullable(requestOptions) .orElse(new CosmosItemRequestOptions()); Mono> responseMessageMono = this.readItemHelper(id, partitionKey, options, false); CosmosItemSerializer effectiveItemSerializer = cosmosEncryptionAsyncClient.getEffectiveItemSerializer(options.getCustomItemSerializer()); return responseMessageMono.publishOn(encryptionScheduler).flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse, this.encryptionProcessor.decrypt( cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse), CosmosItemSerializer.DEFAULT_SERIALIZER)) .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse, classType, effectiveItemSerializer))); } /** * Query for items in the current container. *

* After subscription the operation will be performed. The {@link CosmosPagedFlux} will * contain one or several feed response of the obtained items. In case of * failure the {@link CosmosPagedFlux} will error. * * @param the type parameter. * @param query the query. * @param classType the class type. * @return a {@link CosmosPagedFlux} containing one or several feed response pages of the obtained items or an * error. */ public CosmosPagedFlux queryItems(String query, Class classType) { return this.queryItems(new SqlQuerySpec(query), classType); } /** * Query for items in the current container using a string. *

* After subscription the operation will be performed. The {@link CosmosPagedFlux} will * contain one or several feed response of the obtained items. In case of * failure the {@link CosmosPagedFlux} will error. * * @param the type parameter. * @param query the query. * @param requestOptions the query request options. * @param classType the class type. * @return a {@link CosmosPagedFlux} containing one or several feed response pages of the obtained items or an * error. */ public CosmosPagedFlux queryItems(String query, CosmosQueryRequestOptions requestOptions, Class classType) { if (requestOptions == null) { requestOptions = new CosmosQueryRequestOptions(); } return this.queryItems(new SqlQuerySpec(query), requestOptions, classType); } /** * Query for items in the current container using a {@link SqlQuerySpec}. *

* After subscription the operation will be performed. The {@link CosmosPagedFlux} will * contain one or several feed response of the obtained items. In case of * failure the {@link CosmosPagedFlux} will error. * * @param the type parameter. * @param querySpec the SQL query specification. * @param classType the class type. * @return a {@link CosmosPagedFlux} containing one or several feed response pages of the obtained items or an * error. */ public CosmosPagedFlux queryItems(SqlQuerySpec querySpec, Class classType) { return queryItemsHelper(querySpec, new CosmosQueryRequestOptions(), classType, false); } /** * Query for items in the current container using a {@link SqlQuerySpec} and {@link CosmosQueryRequestOptions}. *

* After subscription the operation will be performed. The {@link Flux} will * contain one or several feed response of the obtained items. In case of * failure the {@link CosmosPagedFlux} will error. * * @param the type parameter. * @param query the SQL query specification. * @param requestOptions the query request options. * @param classType the class type. * @return a {@link CosmosPagedFlux} containing one or several feed response pages of the obtained items or an * error. */ public CosmosPagedFlux queryItems(SqlQuerySpec query, CosmosQueryRequestOptions requestOptions, Class classType) { if (requestOptions == null) { requestOptions = new CosmosQueryRequestOptions(); } return queryItemsHelper(query, requestOptions, classType,false); } /** * Query for items in the current container using a {@link SqlQuerySpecWithEncryption}. *

* After subscription the operation will be performed. The {@link CosmosPagedFlux} will contain one or several feed * response of the obtained items. In case of failure the {@link CosmosPagedFlux} will error. * * @param the type parameter. * @param sqlQuerySpecWithEncryption the sqlQuerySpecWithEncryption. * @param options the query request options. * @param classType the class type. * @return a {@link CosmosPagedFlux} containing one or several feed response pages of the obtained items or an * error. */ public CosmosPagedFlux queryItemsOnEncryptedProperties(SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption, CosmosQueryRequestOptions options, Class classType) { if (options == null) { options = new CosmosQueryRequestOptions(); } if (specWithEncryptionAccessor.getEncryptionParamMap(sqlQuerySpecWithEncryption).size() > 0) { List> encryptionSqlParameterMonoList = new ArrayList<>(); for (Map.Entry entry : specWithEncryptionAccessor.getEncryptionParamMap(sqlQuerySpecWithEncryption).entrySet()) { encryptionSqlParameterMonoList.add(specWithEncryptionAccessor.addEncryptionParameterAsync(sqlQuerySpecWithEncryption, entry.getKey(), entry.getValue(), this)); } Mono> listMono = Flux.mergeSequential(encryptionSqlParameterMonoList).collectList(); Mono sqlQuerySpecMono = listMono.flatMap(ignoreVoids -> Mono.just(specWithEncryptionAccessor.getSqlQuerySpec(sqlQuerySpecWithEncryption))); return queryItemsHelperWithMonoSqlQuerySpec(sqlQuerySpecMono, sqlQuerySpecWithEncryption, options, classType, false); } else { return queryItemsHelper(specWithEncryptionAccessor.getSqlQuerySpec(sqlQuerySpecWithEncryption), options, classType, false); } } /** * Query for items in the change feed of the current container using the {@link CosmosChangeFeedRequestOptions}. *

* After subscription the operation will be performed. The {@link Flux} will * contain one or several feed response of the obtained items. In case of * failure the {@link CosmosPagedFlux} will error. * * @param the type parameter. * @param options the change feed request options. * @param classType the class type. * @return a {@link CosmosPagedFlux} containing one or several feed response pages of the obtained * items or an error. */ // TODO Make this api public once it is GA in cosmos core library CosmosPagedFlux queryChangeFeed(CosmosChangeFeedRequestOptions options, Class classType) { checkNotNull(options, "Argument 'options' must not be null."); checkNotNull(classType, "Argument 'classType' must not be null."); return queryChangeFeedHelper(options, classType,false); } /** * Run patch operations on an Item. *

* After subscription the operation will be performed. * The {@link Mono} upon successful completion will contain a single Cosmos item response with the patched item. * * @param the type parameter. * @param itemId the item id. * @param partitionKey the partition key. * @param cosmosPatchOperations Represents a container having list of operations to be sequentially applied to the referred Cosmos item. * @param options the request options. * @param itemType the item type. * * @return an {@link Mono} containing the Cosmos item resource response with the patched item or an error. */ public Mono> patchItem( String itemId, PartitionKey partitionKey, CosmosPatchOperations cosmosPatchOperations, CosmosPatchItemRequestOptions options, Class itemType) { checkNotNull(itemId, "expected non-null itemId"); checkNotNull(partitionKey, "expected non-null partitionKey for patchItem"); checkNotNull(cosmosPatchOperations, "expected non-null cosmosPatchOperations"); final CosmosPatchItemRequestOptions patchOptions = Optional.ofNullable(options) .orElse(new CosmosPatchItemRequestOptions()); return patchItemHelper(itemId, partitionKey, cosmosPatchOperations, patchOptions, itemType); } private Mono> patchItemHelper(String itemId, PartitionKey partitionKey, CosmosPatchOperations cosmosPatchOperations, CosmosPatchItemRequestOptions options, Class itemType) { this.setRequestHeaders(options); List> monoList = new ArrayList<>(); List operations = cosmosPatchOperationsAccessor.getPatchOperations(cosmosPatchOperations); List operationsSnapshot; synchronized (operations) { operationsSnapshot = new ArrayList<>(operations); } for (PatchOperation patchOperation : operationsSnapshot) { Mono itemPatchOperationMono = null; if (patchOperation.getOperationType() == PatchOperationType.REMOVE) { itemPatchOperationMono = Mono.just(patchOperation); } else if (patchOperation.getOperationType() == PatchOperationType.INCREMENT) { throw new IllegalArgumentException("Increment patch operation is not allowed for encrypted path"); } else if (patchOperation instanceof PatchOperationCore) { JsonNode objectNode = EncryptionUtils.getSimpleObjectMapper().valueToTree(((PatchOperationCore)patchOperation).getResource()); itemPatchOperationMono = encryptionProcessor.encryptPatchNode(objectNode, ((PatchOperationCore)patchOperation).getPath()).map(encryptedObjectNode -> { return new PatchOperationCore<>( patchOperation.getOperationType(), ((PatchOperationCore)patchOperation).getPath(), encryptedObjectNode ); }); } monoList.add(itemPatchOperationMono); } Mono> encryptedPatchOperationsListMono = Flux.mergeSequential(monoList).collectList(); CosmosPatchItemRequestOptions finalRequestOptions = options; CosmosPatchOperations encryptedCosmosPatchOperations = CosmosPatchOperations.create(); return encryptedPatchOperationsListMono.flatMap(patchOperations -> { List snapshot = cosmosPatchOperationsAccessor.getPatchOperations(encryptedCosmosPatchOperations); synchronized(snapshot) { snapshot.addAll(patchOperations); } return patchItemInternalHelper( itemId, partitionKey, encryptedCosmosPatchOperations, finalRequestOptions,itemType, false); }); } @SuppressWarnings("unchecked") // Casting cosmosItemResponse to CosmosItemResponse from CosmosItemResponse private Mono> patchItemInternalHelper(String itemId, PartitionKey partitionKey, CosmosPatchOperations encryptedCosmosPatchOperations, CosmosPatchItemRequestOptions requestOptions, Class itemType, boolean isRetry) { setRequestHeaders(requestOptions); CosmosItemSerializer effectiveItemSerializer = cosmosEncryptionAsyncClient.getEffectiveItemSerializer(requestOptions.getCustomItemSerializer()); CosmosPatchItemRequestOptions requestOptionsWithDefaultSerializer = requestOptions != null ? (CosmosPatchItemRequestOptions)cosmosItemRequestOptionsAccessor .clonePatchItemRequestOptions(requestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER) : new CosmosPatchItemRequestOptions(); return this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptionSettings -> Mono.zip( checkAndGetEncryptedId(itemId, encryptionSettings), checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings)) .flatMap(encryptedIdPartitionKeyTuple -> this.container.patchItem(encryptedIdPartitionKeyTuple.getT1(), encryptedIdPartitionKeyTuple.getT2(), encryptedCosmosPatchOperations, requestOptionsWithDefaultSerializer, itemType).publishOn(encryptionScheduler). flatMap(cosmosItemResponse -> setByteArrayContent((CosmosItemResponse) cosmosItemResponse, this.encryptionProcessor.decrypt( cosmosItemResponseBuilderAccessor.getByteArrayContent((CosmosItemResponse) cosmosItemResponse), CosmosItemSerializer.DEFAULT_SERIALIZER)) .map(bytes -> this.responseFactory.createItemResponse((CosmosItemResponse) cosmosItemResponse, itemType, effectiveItemSerializer))).onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> patchItemInternalHelper(itemId, partitionKey, encryptedCosmosPatchOperations, requestOptions, itemType, true))); } } return Mono.error(exception); }))); } /** * Get the CosmosEncryptionAsyncClient * * @return encrypted cosmosAsyncClient */ CosmosEncryptionAsyncClient getCosmosEncryptionAsyncClient() { return cosmosEncryptionAsyncClient; } /** * Gets the CosmosAsyncContainer * * @return cosmos container */ public CosmosAsyncContainer getCosmosAsyncContainer() { return container; } byte[] cosmosSerializerToStream(T item, CosmosItemSerializer effectiveSerializer) { return EncryptionUtils.serializeJsonToByteArray(effectiveSerializer, item); } CosmosItemSerializer getEffectiveItemSerializer(CosmosItemRequestOptions requestOptions) { return getEffectiveItemSerializer( requestOptions != null ? requestOptions.getCustomItemSerializer() : null); } CosmosItemSerializer getEffectiveItemSerializer(CosmosItemSerializer requestLevelItemSerializer) { return CosmosBridgeInternal .getAsyncDocumentClient(container.getDatabase()) .getEffectiveItemSerializer(requestLevelItemSerializer); } Mono decryptResponseNode( JsonNode jsonNode) { if (jsonNode == null) { return Mono.empty(); } return this.encryptionProcessor.decryptJsonNode( jsonNode); } private Mono> setByteArrayContent(CosmosItemResponse rsp, Mono> bytesMono) { return bytesMono.flatMap( bytes -> { cosmosItemResponseBuilderAccessor.setByteArrayContent(rsp, bytes); return Mono.just(rsp); } ).defaultIfEmpty(rsp); } private Mono> readItemHelper(String id, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions, boolean isRetry) { this.setRequestHeaders(requestOptions); Mono> responseMessageMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptionSettings -> Mono.zip( checkAndGetEncryptedId(id, encryptionSettings), checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings)) .flatMap(encryptedIdPartitionKeyTuple -> this.container.readItem( encryptedIdPartitionKeyTuple.getT1(), encryptedIdPartitionKeyTuple.getT2(), requestOptions, byte[].class))); return responseMessageMono.onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then(Mono.defer(() -> readItemHelper(id, partitionKey, requestOptions, true) )); } } return Mono.error(exception); }); } private Mono> createItemHelper(byte[] streamPayload, CosmosItemRequestOptions requestOptions, Class itemClass, boolean isRetry) { this.setRequestHeaders(requestOptions); CosmosItemSerializer effectiveItemSerializer = cosmosEncryptionAsyncClient.getEffectiveItemSerializer(requestOptions.getCustomItemSerializer()); // The actual replace happens on the already encrypted document // so any custom serialization/deserialization happens here in the encryption wrapper CosmosItemRequestOptions requestOptionsWithDefaultSerializer = ModelBridgeInternal .clone(requestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER); return this.encryptionProcessor.encrypt(streamPayload) .flatMap(encryptedPayload -> this.container.createItem( encryptedPayload, requestOptionsWithDefaultSerializer) .publishOn(encryptionScheduler) .flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse, this.encryptionProcessor.decrypt( cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse), CosmosItemSerializer.DEFAULT_SERIALIZER)) .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse, itemClass, effectiveItemSerializer))).onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> createItemHelper(streamPayload, requestOptions, itemClass, true))); } } return Mono.error(exception); })); } private Mono> createItemHelper(byte[] streamPayload, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions, Class itemClass, boolean isRetry) { this.setRequestHeaders(requestOptions); CosmosItemSerializer effectiveItemSerializer = cosmosEncryptionAsyncClient.getEffectiveItemSerializer(requestOptions.getCustomItemSerializer()); // The actual replace happens on the already encrypted document // so any custom serialization/deserialization happens here in the encryption wrapper CosmosItemRequestOptions requestOptionsWithDefaultSerializer = ModelBridgeInternal .clone(requestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER); AtomicReference encryptedPK = new AtomicReference<>(); Mono encryptedPayloadMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptionSettings -> checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings)) .flatMap(encryptedPartitionKey -> { encryptedPK.set(encryptedPartitionKey); return this.encryptionProcessor.encrypt(streamPayload); }); return encryptedPayloadMono .flatMap(encryptedPayload -> this.container.createItem( encryptedPayload, encryptedPK.get(), requestOptionsWithDefaultSerializer) .publishOn(encryptionScheduler) .flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse, this.encryptionProcessor.decrypt( cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse), CosmosItemSerializer.DEFAULT_SERIALIZER)) .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse, itemClass, effectiveItemSerializer))).onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> createItemHelper(streamPayload, partitionKey, requestOptions, itemClass, true))); } } return Mono.error(exception); })); } private Mono> upsertItemHelper(byte[] streamPayload, CosmosItemRequestOptions requestOptions, Class itemClass, boolean isRetry) { this.setRequestHeaders(requestOptions); CosmosItemSerializer effectiveItemSerializer = cosmosEncryptionAsyncClient.getEffectiveItemSerializer(requestOptions.getCustomItemSerializer()); // The actual replace happens on the already encrypted document // so any custom serialization/deserialization happens her in the encryption wrapper CosmosItemRequestOptions requestOptionsWithDefaultSerializer = ModelBridgeInternal .clone(requestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER); return this.encryptionProcessor.encrypt(streamPayload) .flatMap(encryptedPayload -> this.container.upsertItem( encryptedPayload, requestOptionsWithDefaultSerializer) .publishOn(encryptionScheduler) .flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse, this.encryptionProcessor.decrypt( cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse), CosmosItemSerializer.DEFAULT_SERIALIZER)) .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse, itemClass, effectiveItemSerializer))) .onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> upsertItemHelper(streamPayload, requestOptions, itemClass, true))); } } return Mono.error(exception); })); } private Mono> upsertItemHelper(byte[] streamPayload, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions, Class itemClass, boolean isRetry) { this.setRequestHeaders(requestOptions); CosmosItemSerializer effectiveItemSerializer = cosmosEncryptionAsyncClient.getEffectiveItemSerializer(requestOptions.getCustomItemSerializer()); // The actual replace happens on the already encrypted document // so any custom serialization/deserialization happens her in the encryption wrapper CosmosItemRequestOptions requestOptionsWithDefaultSerializer = ModelBridgeInternal .clone(requestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER); AtomicReference encryptedPK = new AtomicReference<>(); Mono encryptedPayloadMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptionSettings -> checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings)) .flatMap(encryptedPartitionKey -> { encryptedPK.set(encryptedPartitionKey); return this.encryptionProcessor.encrypt(streamPayload); }); return encryptedPayloadMono .flatMap(encryptedPayload -> this.container.upsertItem( encryptedPayload, encryptedPK.get(), requestOptionsWithDefaultSerializer) .publishOn(encryptionScheduler) .flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse, this.encryptionProcessor.decrypt( cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse), CosmosItemSerializer.DEFAULT_SERIALIZER)) .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse, itemClass, effectiveItemSerializer))) .onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> upsertItemHelper(streamPayload, partitionKey, requestOptions, itemClass, true))); } } return Mono.error(exception); })); } private Mono> replaceItemHelper(byte[] streamPayload, String itemId, PartitionKey partitionKey, CosmosItemRequestOptions requestOptions, Class itemClass, boolean isRetry) { this.setRequestHeaders(requestOptions); CosmosItemSerializer effectiveItemSerializer = cosmosEncryptionAsyncClient.getEffectiveItemSerializer(requestOptions.getCustomItemSerializer()); AtomicReference encryptedPK = new AtomicReference<>(); AtomicReference encryptedId = new AtomicReference<>(); Mono encryptedPayloadMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptionSettings -> Mono.zip( checkAndGetEncryptedId(itemId, encryptionSettings), checkAndGetEncryptedPartitionKey(partitionKey, encryptionSettings))) .flatMap(encryptedIdPartitionKeyTuple -> { encryptedId.set(encryptedIdPartitionKeyTuple.getT1()); encryptedPK.set(encryptedIdPartitionKeyTuple.getT2()); return this.encryptionProcessor.encrypt(streamPayload); }); // The actual replace happens on the already encrypted document // so any custom serialization/deserialization happens her in the encryption wrapper CosmosItemRequestOptions requestOptionsWithDefaultSerializer = ModelBridgeInternal .clone(requestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER); return encryptedPayloadMono .flatMap(encryptedPayload -> this.container.replaceItem( encryptedPayload, encryptedId.get(), encryptedPK.get(), requestOptionsWithDefaultSerializer) .publishOn(encryptionScheduler) .flatMap(cosmosItemResponse -> setByteArrayContent(cosmosItemResponse, this.encryptionProcessor.decrypt( cosmosItemResponseBuilderAccessor.getByteArrayContent(cosmosItemResponse), CosmosItemSerializer.DEFAULT_SERIALIZER)) .map(bytes -> this.responseFactory.createItemResponse(cosmosItemResponse, itemClass, effectiveItemSerializer))) .onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> replaceItemHelper(streamPayload, itemId, partitionKey, requestOptions, itemClass, true))); } } return Mono.error(exception); })); } private CosmosPagedFlux queryItemsHelper(SqlQuerySpec sqlQuerySpec, CosmosQueryRequestOptions options, Class classType, boolean isRetry) { CosmosItemSerializer effectiveSerializer = this .getCosmosEncryptionAsyncClient() .getEffectiveItemSerializer(options != null ? options.getCustomItemSerializer(): null); return UtilBridgeInternal.createCosmosPagedFlux(pagedFluxOptions -> { AtomicBoolean shouldRetry = new AtomicBoolean(!isRetry); Transformer transformer = new CosmosEncryptionQueryTransformer( this.encryptionScheduler, this.getEncryptionProcessor(), classType, false); Flux> result = this.transformQueryItemsInternal( transformer, sqlQuerySpec, options, effectiveSerializer ).apply(pagedFluxOptions); return result .onErrorResume(exception -> { if (exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (shouldRetry.get() && isIncorrectContainerRid(cosmosException)) { // stale cache, refresh caches and then retry this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); shouldRetry.set(false); return this.encryptionProcessor .initializeEncryptionSettingsAsync(true) .thenMany( Flux.defer(() -> { return this.transformQueryItemsInternal( transformer, sqlQuerySpec, options, effectiveSerializer ).apply(pagedFluxOptions); }) ); } } return Mono.error(exception); }); }); } private Function>> transformQueryItemsInternal( Transformer transformer, SqlQuerySpec sqlQuerySpec, CosmosQueryRequestOptions queryRequestOptions, CosmosItemSerializer effectiveSerializer) { CosmosQueryRequestOptions finalOptions = setRequestHeaders(cosmosQueryRequestOptionsAccessor .clone(queryRequestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER)); return transformer.transform( cosmosAsyncContainerAccessor.queryItemsInternalFunc( this.container, sqlQuerySpec, finalOptions, JsonNode.class), effectiveSerializer ); } private Function>> transformQueryChangeFeedInternal( Transformer transformer, CosmosChangeFeedRequestOptions changeFeedRequestOptions, CosmosPagedFluxOptions pagedFluxOptions, CosmosItemSerializer effectiveSerializer) { CosmosChangeFeedRequestOptions finalOptions = setRequestHeaders( cosmosChangeFeedRequestOptionsAccessor.clone(changeFeedRequestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER)); getEffectiveCosmosChangeFeedRequestOptions(pagedFluxOptions, finalOptions); return transformer.transform( cosmosAsyncContainerAccessor .queryChangeFeedInternalFunc( this.container, finalOptions, JsonNode.class), effectiveSerializer ); } private Function>> transformQueryItemsWithMonoSqlQuerySpec( Transformer transformer, Mono sqlQuerySpecMono, CosmosQueryRequestOptions options, CosmosItemSerializer effectiveSerializer) { CosmosQueryRequestOptions finalOptions = setRequestHeaders(cosmosQueryRequestOptionsAccessor .clone(options) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER)); return transformer.transform( cosmosAsyncContainerAccessor.queryItemsInternalFuncWithMonoSqlQuerySpec( this.container, sqlQuerySpecMono, finalOptions, JsonNode.class ), effectiveSerializer ); } private CosmosPagedFlux queryChangeFeedHelper(CosmosChangeFeedRequestOptions options, Class classType, boolean isRetry) { CosmosItemSerializer effectiveSerializer = this .getCosmosEncryptionAsyncClient() .getEffectiveItemSerializer(options != null ? options.getCustomItemSerializer() : null); return UtilBridgeInternal.createCosmosPagedFlux(pagedFluxOptions -> { AtomicBoolean shouldRetry = new AtomicBoolean(!isRetry); Transformer transformer = new CosmosEncryptionQueryTransformer( this.encryptionScheduler, this.getEncryptionProcessor(), classType, true); Flux> result = this.transformQueryChangeFeedInternal( transformer, options, pagedFluxOptions, effectiveSerializer ).apply(pagedFluxOptions); return result .onErrorResume(exception -> { if (exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (shouldRetry.get() && isIncorrectContainerRid(cosmosException)) { // stale cache, refresh caches and then retry this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); shouldRetry.set(false); return this.encryptionProcessor .initializeEncryptionSettingsAsync(true) .thenMany( Flux.defer(() -> { return this.transformQueryChangeFeedInternal( transformer, options, pagedFluxOptions, effectiveSerializer ).apply(pagedFluxOptions); }) ); } } return Mono.error(exception); }); }); } private CosmosPagedFlux queryItemsHelperWithMonoSqlQuerySpec(Mono sqlQuerySpecMono, SqlQuerySpecWithEncryption sqlQuerySpecWithEncryption, CosmosQueryRequestOptions options, Class classType, boolean isRetry) { CosmosItemSerializer effectiveSerializer = this .getCosmosEncryptionAsyncClient() .getEffectiveItemSerializer(options != null ? options.getCustomItemSerializer(): null); return UtilBridgeInternal.createCosmosPagedFlux(pagedFluxOptions -> { AtomicBoolean shouldRetry = new AtomicBoolean(!isRetry); Transformer transformer = new CosmosEncryptionQueryTransformer( this.encryptionScheduler, this.getEncryptionProcessor(), classType, false); Flux> result = this.transformQueryItemsWithMonoSqlQuerySpec( transformer, sqlQuerySpecMono, options, effectiveSerializer ).apply(pagedFluxOptions); return result .onErrorResume(exception -> { if (exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (shouldRetry.get() && isIncorrectContainerRid(cosmosException)) { // stale cache, refresh caches and then retry this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); shouldRetry.set(false); return this.encryptionProcessor .initializeEncryptionSettingsAsync(true) .thenMany( Flux.defer(() -> this.transformQueryItemsInternal( transformer, specWithEncryptionAccessor.getSqlQuerySpec(sqlQuerySpecWithEncryption), options, effectiveSerializer ).apply(pagedFluxOptions) ) ); } } return Mono.error(exception); }); }); } /** * Executes the encrypted transactional batch. * * @param cosmosBatch Batch having list of operation and partition key which will be executed by this container. * * @return A Mono response which contains details of execution of the transactional batch. *

* If the transactional batch executes successfully, the value returned by {@link * CosmosBatchResponse#getStatusCode} on the response returned will be set to 200}. *

* If an operation within the transactional batch fails during execution, no changes from the batch will be * committed and the status of the failing operation is made available by {@link * CosmosBatchResponse#getStatusCode} or by the exception. To obtain information about the operations * that failed in case of some user error like conflict, not found etc, the response can be enumerated. * This returns {@link CosmosBatchOperationResult} instances corresponding to each operation in the * transactional batch in the order they were added to the transactional batch. * For a result corresponding to an operation within the transactional batch, use * {@link CosmosBatchOperationResult#getStatusCode} * to access the status of the operation. If the operation was not executed or it was aborted due to the failure of * another operation within the transactional batch, the value of this field will be 424; * for the operation that caused the batch to abort, the value of this field * will indicate the cause of failure. *

* If there are issues such as request timeouts, Gone, session not available, network failure * or if the service somehow returns 5xx then the Mono will return error instead of CosmosBatchResponse. *

* Use {@link CosmosBatchResponse#isSuccessStatusCode} on the response returned to ensure that the * transactional batch succeeded. */ public Mono executeCosmosBatch(CosmosBatch cosmosBatch) { return this.executeCosmosBatch(cosmosBatch, new CosmosBatchRequestOptions()); } /** * Executes the encrypted transactional batch. * * @param cosmosBatch Batch having list of operation and partition key which will be executed by this container. * @param requestOptions Options that apply specifically to batch request. * * @return A Mono response which contains details of execution of the transactional batch. *

* If the transactional batch executes successfully, the value returned by {@link * CosmosBatchResponse#getStatusCode} on the response returned will be set to 200}. *

* If an operation within the transactional batch fails during execution, no changes from the batch will be * committed and the status of the failing operation is made available by {@link * CosmosBatchResponse#getStatusCode} or by the exception. To obtain information about the operations * that failed in case of some user error like conflict, not found etc, the response can be enumerated. * This returns {@link CosmosBatchOperationResult} instances corresponding to each operation in the * transactional batch in the order they were added to the transactional batch. * For a result corresponding to an operation within the transactional batch, use * {@link CosmosBatchOperationResult#getStatusCode} * to access the status of the operation. If the operation was not executed or it was aborted due to the failure of * another operation within the transactional batch, the value of this field will be 424; * for the operation that caused the batch to abort, the value of this field * will indicate the cause of failure. *

* If there are issues such as request timeouts, Gone, session not available, network failure * or if the service somehow returns 5xx then the Mono will return error instead of CosmosBatchResponse. *

* Use {@link CosmosBatchResponse#isSuccessStatusCode} on the response returned to ensure that the * transactional batch succeeded. */ public Mono executeCosmosBatch(CosmosBatch cosmosBatch, CosmosBatchRequestOptions requestOptions) { final CosmosBatchRequestOptions cosmosBatchRequestOptions = Optional.ofNullable(requestOptions) .orElse(new CosmosBatchRequestOptions()); final CosmosItemSerializer effectiveItemSerializer = this .getEffectiveItemSerializer(cosmosBatchRequestOptions.getCustomItemSerializer()); List>> monoList = new ArrayList<>(); for (ItemBatchOperation itemBatchOperation : cosmosBatchAccessor.getOperationsInternal(cosmosBatch)) { Mono> itemBatchOperationMono = null; if (itemBatchOperation.getItem() != null) { itemBatchOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptionSettings -> { try { String idValue = getIdValue(itemBatchOperation); return Mono.zip( checkAndGetEncryptedId(idValue, encryptionSettings), checkAndGetEncryptedPartitionKey(itemBatchOperation.getPartitionKeyValue(), encryptionSettings)); } catch (IllegalAccessException | NoSuchFieldException e) { return Mono.error(e); } }) .flatMap(encryptedIdPartitionKeyTuple -> { Map jsonTree = itemSerializerAccessor.serializeSafe( effectiveItemSerializer, itemBatchOperation.getItem()); ObjectNode objectNode = jsonTree instanceof ObjectNodeMap ? ((ObjectNodeMap)jsonTree).getObjectNode().deepCopy() : EncryptionUtils.getSimpleObjectMapper().valueToTree(jsonTree); return encryptionProcessor.encryptObjectNode(objectNode).map(encryptedItem -> new ItemBatchOperation<>( itemBatchOperation.getOperationType(), encryptedIdPartitionKeyTuple.getT1(), encryptedIdPartitionKeyTuple.getT2(), itemBatchOperation.getRequestOptions(), encryptedItem )); }); } else { itemBatchOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap(encryptionSettings -> { return Mono.zip( checkAndGetEncryptedId(itemBatchOperation.getId(), encryptionSettings), checkAndGetEncryptedPartitionKey(itemBatchOperation.getPartitionKeyValue(), encryptionSettings)); }) .flatMap(encryptedIdPartitionKeyTuple -> Mono.just( new ItemBatchOperation<>( itemBatchOperation.getOperationType(), encryptedIdPartitionKeyTuple.getT1(), encryptedIdPartitionKeyTuple.getT2(), itemBatchOperation.getRequestOptions(), null ))); } monoList.add(itemBatchOperationMono); } Mono>> encryptedOperationListMono = Flux.mergeSequential(monoList).collectList(); CosmosBatch encryptedCosmosBatch = CosmosBatch.createCosmosBatch(cosmosBatch.getPartitionKeyValue()); CosmosBatchRequestOptions batchRequestOptionsWithDefaultSerializer = cosmosBatchRequestOptionsAccessor .clone(cosmosBatchRequestOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER); return encryptedOperationListMono.flatMap(itemBatchOperations -> { cosmosBatchAccessor.getOperationsInternal(encryptedCosmosBatch).addAll(itemBatchOperations); return executeCosmosBatchHelper( encryptedCosmosBatch, batchRequestOptionsWithDefaultSerializer, effectiveItemSerializer, false); }); } private Mono executeCosmosBatchHelper(CosmosBatch encryptedCosmosBatch, CosmosBatchRequestOptions requestOptions, CosmosItemSerializer effectiveItemSerializer, boolean isRetry) { setRequestHeaders(requestOptions); return this.container.executeCosmosBatch(encryptedCosmosBatch, requestOptions).flatMap(cosmosBatchResponse -> { // TODO this should check for BadRequest StatusCode too, requires a service fix to return 400 instead of // -1 which is currently returned inside the body. // Once fixed from service below if condition can be removed, as this is already covered in onErrorResume. if (!isRetry && cosmosBatchResponse.getSubStatusCode() == 1024) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> executeCosmosBatchHelper( encryptedCosmosBatch, requestOptions, effectiveItemSerializer, true))); } List> decryptMonoList = new ArrayList<>(); for (CosmosBatchOperationResult cosmosBatchOperationResult : cosmosBatchResponseAccessor.getResults(cosmosBatchResponse)) { ObjectNode objectNode = cosmosBatchOperationResultAccessor.getResourceObject(cosmosBatchOperationResult); if (objectNode != null) { decryptMonoList.add(encryptionProcessor.decryptJsonNode(objectNode).flatMap(jsonNode -> { cosmosBatchOperationResultAccessor.setResourceObject(cosmosBatchOperationResult, (ObjectNode) jsonNode); cosmosBatchOperationResultAccessor.setEffectiveItemSerializer( cosmosBatchOperationResult, effectiveItemSerializer ); return Mono.empty(); })); } } Mono> listMono = Flux.mergeSequential(decryptMonoList).collectList(); return listMono.map(aVoid -> cosmosBatchResponse); }).onErrorResume(exception -> { if (!isRetry && exception instanceof CosmosException) { final CosmosException cosmosException = (CosmosException) exception; if (isIncorrectContainerRid(cosmosException)) { this.encryptionProcessor.getIsEncryptionSettingsInitDone().set(false); return this.encryptionProcessor.initializeEncryptionSettingsAsync(true).then (Mono.defer(() -> executeCosmosBatchHelper( encryptedCosmosBatch, requestOptions, effectiveItemSerializer, true))); } } return Mono.error(exception); }); } /** * Executes flux of operations in Bulk. * * @param The context for the bulk processing. * @param operations Flux of operation which will be executed by this container. * * @return A Flux of {@link CosmosBulkOperationResponse} which contains operation and it's response or exception. *

* To create a operation which can be executed here, use {@link com.azure.cosmos.models.CosmosBulkOperations}. For eg. * for a upsert operation use {@link com.azure.cosmos.models.CosmosBulkOperations#getUpsertItemOperation(Object, PartitionKey)} *

*

* We can get the corresponding operation using {@link CosmosBulkOperationResponse#getOperation()} and * it's response using {@link CosmosBulkOperationResponse#getResponse()}. If the operation was executed * successfully, the value returned by {@link com.azure.cosmos.models.CosmosBulkItemResponse#isSuccessStatusCode()} will be true. To get * actual status use {@link com.azure.cosmos.models.CosmosBulkItemResponse#getStatusCode()}. *

* To check if the operation had any exception, use {@link CosmosBulkOperationResponse#getException()} to * get the exception. */ public Flux> executeBulkOperations( Flux operations) { return this.executeBulkOperations(operations, new CosmosBulkExecutionOptions()); } private static String getIdValue(CosmosItemOperation itemOperation) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException { String idSnapshot = itemOperation.getId(); if (!Strings.isNullOrEmpty(idSnapshot)) { return idSnapshot; } Class itemClass = itemOperation.getItem().getClass(); if (ObjectNode.class.isAssignableFrom(itemClass)) { return ((ObjectNode)itemOperation.getItem()).get("id").textValue(); } if (JsonSerializable.class.isAssignableFrom(itemClass)) { return ((JsonSerializable)itemOperation.getItem()).get(Constants.PROPERTY_NAME_ID).toString(); } Field id = itemClass.getDeclaredField(Constants.PROPERTY_NAME_ID); id.setAccessible(true); return (String) id.get(itemOperation.getItem()); } /** * Executes flux of operations in Bulk. * * @param The context for the bulk processing. * * @param operations Flux of operation which will be executed by this container. * @param bulkOptions Options that apply for this Bulk request which specifies options regarding execution like * concurrency, batching size, interval and context. * * @return A Flux of {@link CosmosBulkOperationResponse} which contains operation and it's response or exception. *

* To create a operation which can be executed here, use {@link com.azure.cosmos.models.CosmosBulkOperations}. For eg. * for a upsert operation use {@link com.azure.cosmos.models.CosmosBulkOperations#getUpsertItemOperation(Object, PartitionKey)} *

*

* We can get the corresponding operation using {@link CosmosBulkOperationResponse#getOperation()} and * it's response using {@link CosmosBulkOperationResponse#getResponse()}. If the operation was executed * successfully, the value returned by {@link com.azure.cosmos.models.CosmosBulkItemResponse#isSuccessStatusCode()} will be true. To get * actual status use {@link com.azure.cosmos.models.CosmosBulkItemResponse#getStatusCode()}. *

* To check if the operation had any exception, use {@link CosmosBulkOperationResponse#getException()} to * get the exception. */ @SuppressWarnings("unchecked") public Flux> executeBulkOperations( Flux operations, CosmosBulkExecutionOptions bulkOptions) { final CosmosBulkExecutionOptions cosmosBulkExecutionOptions = Optional.ofNullable(bulkOptions) .orElse(new CosmosBulkExecutionOptions()); CosmosItemSerializer effectiveItemSerializer = this.getEffectiveItemSerializer(cosmosBulkExecutionOptions.getCustomItemSerializer()); Flux operationFlux = operations.flatMap(cosmosItemOperation -> { Mono cosmosItemOperationMono = null; if (cosmosItemOperation.getItem() != null) { cosmosItemOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap( encryptionSettings -> { try { String idValue = getIdValue(cosmosItemOperation); return Mono.zip( checkAndGetEncryptedId(idValue, encryptionSettings), checkAndGetEncryptedPartitionKey(cosmosItemOperation.getPartitionKeyValue(), encryptionSettings)); } catch (IllegalAccessException | NoSuchFieldException e) { return Mono.error(e); } }) .flatMap(encryptedIdPartitionKeyTuple -> { Map jsonTree = itemSerializerAccessor.serializeSafe( effectiveItemSerializer, cosmosItemOperation.getItem()); ObjectNode objectNode = jsonTree instanceof ObjectNodeMap ? ((ObjectNodeMap)jsonTree).getObjectNode().deepCopy() : EncryptionUtils.getSimpleObjectMapper().valueToTree(jsonTree); assert cosmosItemOperation instanceof ItemBulkOperation; return this.encryptionProcessor.encryptObjectNode(objectNode).map(encryptedItem -> new ItemBulkOperation<>( cosmosItemOperation.getOperationType(), encryptedIdPartitionKeyTuple.getT1(), encryptedIdPartitionKeyTuple.getT2(), ((ItemBulkOperation) cosmosItemOperation).getRequestOptions(), encryptedItem, cosmosItemOperation.getContext() )); }); } else { cosmosItemOperationMono = this.encryptionProcessor.initEncryptionSettingsIfNotInitializedAsync() .thenReturn(this.encryptionProcessor.getEncryptionSettings()) .flatMap( encryptionSettings -> Mono.zip( checkAndGetEncryptedId(cosmosItemOperation.getId() , encryptionSettings), checkAndGetEncryptedPartitionKey(cosmosItemOperation.getPartitionKeyValue(), encryptionSettings))) .flatMap(encryptedIdPartitionKeyTuple -> Mono.just( new ItemBulkOperation<>( cosmosItemOperation.getOperationType(), encryptedIdPartitionKeyTuple.getT1(), encryptedIdPartitionKeyTuple.getT2(), ((ItemBulkOperation) cosmosItemOperation).getRequestOptions(), null, cosmosItemOperation.getContext() ))); } return cosmosItemOperationMono; }); Mono> listMono = operationFlux.collectList(); setRequestHeaders(cosmosBulkExecutionOptions); operationFlux = listMono.flatMapMany(Flux::fromIterable); CosmosBulkExecutionOptions executionOptionsWithDefaultSerializer = cosmosBulkExecutionOptionsAccessor .clone(cosmosBulkExecutionOptions) .setCustomItemSerializer(CosmosItemSerializer.DEFAULT_SERIALIZER); return executeBulkOperationsHelper( operationFlux, executionOptionsWithDefaultSerializer, effectiveItemSerializer); } @SuppressWarnings("unchecked") private Flux> executeBulkOperationsHelper(Flux operations, CosmosBulkExecutionOptions bulkOptions, CosmosItemSerializer effectiveItemSerializer) { return this.container.executeBulkOperations(operations, bulkOptions).flatMap(cosmosBulkOperationResponse -> { CosmosBulkItemResponse cosmosBulkItemResponse = cosmosBulkOperationResponse.getResponse(); ObjectNode objectNode = cosmosBulkItemResponseAccessor.getResourceObject(cosmosBulkItemResponse); if(objectNode != null) { Mono jsonNodeMono = encryptionProcessor.decryptJsonNode(objectNode).flatMap(jsonNode -> { cosmosBulkItemResponseAccessor.setResourceObject(cosmosBulkItemResponse, (ObjectNode) jsonNode); cosmosBulkItemResponseAccessor.setEffectiveItemSerializer(cosmosBulkItemResponse, effectiveItemSerializer); return Mono.just(jsonNode); }); return jsonNodeMono.flux().flatMap(jsonNode -> Flux.just((CosmosBulkOperationResponse) cosmosBulkOperationResponse)); } return Mono.just((CosmosBulkOperationResponse) cosmosBulkOperationResponse); }); } private void setRequestHeaders(CosmosItemRequestOptions requestOptions) { cosmosItemRequestOptionsAccessor.setHeader(requestOptions, Constants.IS_CLIENT_ENCRYPTED_HEADER, "true"); cosmosItemRequestOptionsAccessor.setHeader(requestOptions, Constants.INTENDED_COLLECTION_RID_HEADER, this.encryptionProcessor.getContainerRid()); } private CosmosQueryRequestOptions setRequestHeaders(CosmosQueryRequestOptions requestOptions) { cosmosQueryRequestOptionsAccessor .getImpl(requestOptions) .setHeader( Constants.IS_CLIENT_ENCRYPTED_HEADER, "true"); cosmosQueryRequestOptionsAccessor .getImpl(requestOptions) .setHeader( Constants.INTENDED_COLLECTION_RID_HEADER, this.encryptionProcessor.getContainerRid()); return requestOptions; } private CosmosChangeFeedRequestOptions setRequestHeaders(CosmosChangeFeedRequestOptions requestOptions) { cosmosChangeFeedRequestOptionsAccessor.setHeader(requestOptions, Constants.IS_CLIENT_ENCRYPTED_HEADER, "true"); cosmosChangeFeedRequestOptionsAccessor.setHeader(requestOptions, Constants.INTENDED_COLLECTION_RID_HEADER, this.encryptionProcessor.getContainerRid()); return requestOptions; } private void setRequestHeaders(CosmosBatchRequestOptions requestOptions) { cosmosBatchRequestOptionsAccessor.setHeader(requestOptions, Constants.IS_CLIENT_ENCRYPTED_HEADER, "true"); cosmosBatchRequestOptionsAccessor.setHeader(requestOptions, Constants.INTENDED_COLLECTION_RID_HEADER, this.encryptionProcessor.getContainerRid()); } private void setRequestHeaders(CosmosBulkExecutionOptions requestOptions) { CosmosBulkExecutionOptionsImpl requestOptionsImpl = cosmosBulkExecutionOptionsAccessor.getImpl(requestOptions); requestOptionsImpl.setHeader(Constants.IS_CLIENT_ENCRYPTED_HEADER, "true"); requestOptionsImpl.setHeader(Constants.INTENDED_COLLECTION_RID_HEADER, this.encryptionProcessor.getContainerRid()); } boolean isIncorrectContainerRid(CosmosException cosmosException) { return cosmosException.getStatusCode() == HttpConstants.StatusCodes.BADREQUEST && cosmosException.getResponseHeaders().get(HttpConstants.HttpHeaders.SUB_STATUS) != null && cosmosException.getResponseHeaders().get(HttpConstants.HttpHeaders.SUB_STATUS) .equals(Constants.INCORRECT_CONTAINER_RID_SUB_STATUS); } static { EncryptionImplementationBridgeHelpers.CosmosEncryptionAsyncContainerHelper.setCosmosEncryptionAsyncContainerAccessor(new EncryptionImplementationBridgeHelpers.CosmosEncryptionAsyncContainerHelper.CosmosEncryptionAsyncContainerAccessor() { @Override public EncryptionProcessor getEncryptionProcessor(CosmosEncryptionAsyncContainer cosmosEncryptionAsyncContainer) { return cosmosEncryptionAsyncContainer.getEncryptionProcessor(); } }); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy