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

com.couchbase.client.java.AsyncCollection Maven / Gradle / Ivy

There is a newer version: 3.7.2
Show newest version
/*
 * Copyright (c) 2018 Couchbase, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.couchbase.client.java;

import com.couchbase.client.core.Core;
import com.couchbase.client.core.CoreContext;
import com.couchbase.client.core.annotation.Stability;
import com.couchbase.client.core.cnc.RequestSpan;
import com.couchbase.client.core.cnc.TracingIdentifiers;
import com.couchbase.client.core.config.BucketConfig;
import com.couchbase.client.core.env.TimeoutConfig;
import com.couchbase.client.core.error.InvalidArgumentException;
import com.couchbase.client.core.error.context.ReducedKeyValueErrorContext;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.core.msg.kv.GetAndLockRequest;
import com.couchbase.client.core.msg.kv.GetAndTouchRequest;
import com.couchbase.client.core.msg.kv.GetMetaRequest;
import com.couchbase.client.core.msg.kv.GetRequest;
import com.couchbase.client.core.msg.kv.InsertRequest;
import com.couchbase.client.core.msg.kv.RemoveRequest;
import com.couchbase.client.core.msg.kv.ReplaceRequest;
import com.couchbase.client.core.msg.kv.SubdocCommandType;
import com.couchbase.client.core.msg.kv.SubdocGetRequest;
import com.couchbase.client.core.msg.kv.SubdocMutateRequest;
import com.couchbase.client.core.msg.kv.TouchRequest;
import com.couchbase.client.core.msg.kv.UnlockRequest;
import com.couchbase.client.core.msg.kv.UpsertRequest;
import com.couchbase.client.core.retry.RetryStrategy;
import com.couchbase.client.core.service.kv.ReplicaHelper;
import com.couchbase.client.core.util.BucketConfigUtil;
import com.couchbase.client.java.codec.JsonSerializer;
import com.couchbase.client.java.codec.Transcoder;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.kv.CommonDurabilityOptions;
import com.couchbase.client.java.kv.ExistsAccessor;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.ExistsResult;
import com.couchbase.client.java.kv.Expiry;
import com.couchbase.client.java.kv.GetAccessor;
import com.couchbase.client.java.kv.GetAllReplicasOptions;
import com.couchbase.client.java.kv.GetAndLockOptions;
import com.couchbase.client.java.kv.GetAndTouchOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
import com.couchbase.client.java.kv.GetOptions;
import com.couchbase.client.java.kv.GetReplicaResult;
import com.couchbase.client.java.kv.GetResult;
import com.couchbase.client.java.kv.InsertAccessor;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.LookupInAccessor;
import com.couchbase.client.java.kv.LookupInMacro;
import com.couchbase.client.java.kv.LookupInOptions;
import com.couchbase.client.java.kv.LookupInResult;
import com.couchbase.client.java.kv.LookupInSpec;
import com.couchbase.client.java.kv.MutateInAccessor;
import com.couchbase.client.java.kv.MutateInOptions;
import com.couchbase.client.java.kv.MutateInResult;
import com.couchbase.client.java.kv.MutateInSpec;
import com.couchbase.client.java.kv.MutationResult;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.RemoveAccessor;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplaceAccessor;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.StoreSemantics;
import com.couchbase.client.java.kv.TouchAccessor;
import com.couchbase.client.java.kv.TouchOptions;
import com.couchbase.client.java.kv.UnlockAccessor;
import com.couchbase.client.java.kv.UnlockOptions;
import com.couchbase.client.java.kv.UpsertAccessor;
import com.couchbase.client.java.kv.UpsertOptions;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import static com.couchbase.client.core.util.Validators.notNull;
import static com.couchbase.client.core.util.Validators.notNullOrEmpty;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_EXISTS_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_GET_ALL_REPLICAS_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_GET_AND_LOCK_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_GET_AND_TOUCH_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_GET_ANY_REPLICA_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_GET_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_INSERT_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_LOOKUP_IN_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_MUTATE_IN_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_REMOVE_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_REPLACE_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_TOUCH_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_UNLOCK_OPTIONS;
import static com.couchbase.client.java.ReactiveCollection.DEFAULT_UPSERT_OPTIONS;

/**
 * The {@link AsyncCollection} provides basic asynchronous access to all collection APIs.
 *
 * 

This type of API provides asynchronous support through the concurrency mechanisms * that ship with Java 8 and later, notably the async {@link CompletableFuture}. It is the * async mechanism with the lowest overhead (best performance) but also comes with less * bells and whistles as the {@link ReactiveCollection} for example.

* *

Most of the time we recommend using the {@link ReactiveCollection} unless you need the * last drop of performance or if you are implementing higher level primitives on top of this * one.

* * @since 3.0.0 */ public class AsyncCollection { /** * Holds the underlying core which is used to dispatch operations. */ private final Core core; /** * Holds the core context of the attached core. */ private final CoreContext coreContext; /** * Holds the environment for this collection. */ private final ClusterEnvironment environment; /** * The name of the collection. */ private final String name; /** * The name of the bucket. */ private final String bucket; /** * The name of the associated scope. */ private final String scopeName; /** * Holds the async binary collection object. */ private final AsyncBinaryCollection asyncBinaryCollection; /** * Stores information about the collection. */ private final CollectionIdentifier collectionIdentifier; /** * Creates a new {@link AsyncCollection}. * * @param name the name of the collection. * @param scopeName the name of the scope associated. * @param core the core into which ops are dispatched. * @param environment the surrounding environment for config options. */ AsyncCollection(final String name, final String scopeName, final String bucket, final Core core, final ClusterEnvironment environment) { this.name = name; this.scopeName = scopeName; this.core = core; this.coreContext = core.context(); this.environment = environment; this.bucket = bucket; this.collectionIdentifier = new CollectionIdentifier(bucket, Optional.of(scopeName), Optional.of(name)); this.asyncBinaryCollection = new AsyncBinaryCollection(core, environment, collectionIdentifier); } /** * Provides access to the underlying {@link Core}. */ @Stability.Volatile public Core core() { return core; } /** * Provides access to the underlying {@link ClusterEnvironment}. */ public ClusterEnvironment environment() { return environment; } /** * The name of the collection in use. * * @return the name of the collection. */ public String name() { return name; } /** * Returns the name of the bucket associated with this collection. */ public String bucketName() { return bucket; } /** * Returns the name of the scope associated with this collection. */ public String scopeName() { return scopeName; } /** * Provides access to the binary APIs, not used for JSON documents. * * @return the {@link AsyncBinaryCollection}. */ public AsyncBinaryCollection binary() { return asyncBinaryCollection; } /** * Fetches a full document (or a projection of it) from a collection with default options. * * @param id the document id which is used to uniquely identify it. * @return a {@link CompletableFuture} indicating once loaded or failed. */ public CompletableFuture get(final String id) { return get(id, DEFAULT_GET_OPTIONS); } /** * Fetches a full document (or a projection of it) from a collection with custom options. * * @param id the document id which is used to uniquely identify it. * @param options custom options to change the default behavior. * @return a {@link CompletableFuture} completing once loaded or failed. */ public CompletableFuture get(final String id, final GetOptions options) { notNull(options, "GetOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); final GetOptions.Built opts = options.build(); final Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); if (opts.projections().isEmpty() && !opts.withExpiry()) { return GetAccessor.get(core, fullGetRequest(id, opts), transcoder); } else { return GetAccessor.subdocGet(core, subdocGetRequest(id, opts), transcoder); } } /** * Helper method to create a get request for a full doc fetch. * * @param id the document id which is used to uniquely identify it. * @param opts custom options to change the default behavior. * @return the get request. */ @Stability.Internal GetRequest fullGetRequest(final String id, final GetOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_GET, opts.parentSpan().orElse(null)); GetRequest request = new GetRequest(id, timeout, coreContext, collectionIdentifier, retryStrategy, span); request.context().clientContext(opts.clientContext()); return request; } /** * Helper method to create a get request for a subdoc fetch. * * @param id the document id which is used to uniquely identify it. * @param opts custom options to change the default behavior. * @return the subdoc get request. */ @Stability.Internal SubdocGetRequest subdocGetRequest(final String id, final GetOptions.Built opts) { try { notNullOrEmpty(id, "Id"); if (opts.withExpiry()) { if (opts.projections().size() > 15) { throw InvalidArgumentException.fromMessage("Only a maximum of 16 fields can be " + "projected per request due to a server limitation (includes the expiration macro as one field)."); } } else { if (opts.projections().size() > 16) { throw InvalidArgumentException.fromMessage("Only a maximum of 16 fields can be " + "projected per request due to a server limitation."); } } } catch (Exception cause) { throw new InvalidArgumentException( "Argument validation failed", cause, ReducedKeyValueErrorContext.create(id, collectionIdentifier) ); } Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); List commands = new ArrayList<>(16); if (!opts.projections().isEmpty()) { if (opts.projections().size() > 16) { throw new UnsupportedOperationException("Only a maximum of 16 fields can be " + "projected per request."); } List projections = opts.projections(); for (int i = 0; i < projections.size(); i ++) { commands.add(new SubdocGetRequest.Command(SubdocCommandType.GET, projections.get(i), false, commands.size())); } } else { commands.add(new SubdocGetRequest.Command( SubdocCommandType.GET_DOC, "", false, commands.size() )); } if (opts.withExpiry()) { // xattrs must go first commands.add(0, new SubdocGetRequest.Command( SubdocCommandType.GET, LookupInMacro.EXPIRY_TIME, true, commands.size() )); // If we have projections, there is no need to fetch the flags // since only JSON is supported that implies the flags. // This will also "force" the transcoder on the read side to be // JSON aware since the flags are going to be hard-set to the // JSON compat flags. if (opts.projections().isEmpty()) { commands.add(1, new SubdocGetRequest.Command( SubdocCommandType.GET, LookupInMacro.FLAGS, true, commands.size() )); } } RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_LOOKUP_IN, opts.parentSpan().orElse(null)); SubdocGetRequest request = new SubdocGetRequest( timeout, coreContext, collectionIdentifier, retryStrategy, id, (byte) 0x00, commands, span ); request.context().clientContext(opts.clientContext()); return request; } /** * Fetches a full document and write-locks it for the given duration with default options. *

* Note that the client does not enforce an upper limit on the {@link Duration} lockTime. The maximum lock time * by default on the server is 30 seconds. Any value larger than 30 seconds will be capped down by the server to * the default lock time, which is 15 seconds unless modified on the server side. * * @param id the document id which is used to uniquely identify it. * @param lockTime how long to write-lock the document for (any duration > 30s will be capped to server default of 15s). * @return a {@link CompletableFuture} completing once loaded or failed. */ public CompletableFuture getAndLock(final String id, Duration lockTime) { return getAndLock(id, lockTime, DEFAULT_GET_AND_LOCK_OPTIONS); } /** * Fetches a full document and write-locks it for the given duration with custom options. *

* Note that the client does not enforce an upper limit on the {@link Duration} lockTime. The maximum lock time * by default on the server is 30 seconds. Any value larger than 30 seconds will be capped down by the server to * the default lock time, which is 15 seconds unless modified on the server side. * * @param id the document id which is used to uniquely identify it. * @param lockTime how long to write-lock the document for (any duration > 30s will be capped to server default of 15s). * @param options custom options to change the default behavior. * @return a {@link CompletableFuture} completing once loaded or failed. */ public CompletableFuture getAndLock(final String id, final Duration lockTime, final GetAndLockOptions options) { notNull(options, "GetAndLockOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); GetAndLockOptions.Built opts = options.build(); final Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); return GetAccessor.getAndLock(core, getAndLockRequest(id, lockTime, opts), transcoder); } /** * Helper method to create the get and lock request. * * @param id the document id which is used to uniquely identify it. * @param lockTime how long to lock the document for. Any values above 30 seconds will be * treated as 30 seconds. * @param opts custom options to change the default behavior. * @return the get and lock request. */ @Stability.Internal GetAndLockRequest getAndLockRequest(final String id, final Duration lockTime, final GetAndLockOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(lockTime, "LockTime", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_GET_AND_LOCK, opts.parentSpan().orElse(null)); GetAndLockRequest request = new GetAndLockRequest( id, timeout, coreContext, collectionIdentifier, retryStrategy, lockTime, span ); request.context().clientContext(opts.clientContext()); return request; } /** * Fetches a full document and resets its expiration time to the value provided with default * options. * * @param id the document id which is used to uniquely identify it. * @param expiry the new expiration time for the document. * @return a {@link CompletableFuture} completing once loaded or failed. */ public CompletableFuture getAndTouch(final String id, final Duration expiry) { return getAndTouch(id, expiry, DEFAULT_GET_AND_TOUCH_OPTIONS); } /** * Fetches a full document and resets its expiration time to the value provided with custom * options. * * @param id the document id which is used to uniquely identify it. * @param expiry the new expiration time for the document. * @param options custom options to change the default behavior. * @return a {@link CompletableFuture} completing once loaded or failed. */ public CompletableFuture getAndTouch(final String id, final Duration expiry, final GetAndTouchOptions options) { notNull(expiry, "Expiry", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(options, "GetAndTouchOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); GetAndTouchOptions.Built opts = options.build(); final Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); return GetAccessor.getAndTouch(core, getAndTouchRequest(id, Expiry.relative(expiry), opts), transcoder); } /** * Helper method for get and touch requests. * * @param id the document id which is used to uniquely identify it. * @param expiry the new expiration time for the document. * @param opts custom options to change the default behavior. * @return the get and touch request. */ @Stability.Internal GetAndTouchRequest getAndTouchRequest(final String id, final Expiry expiry, final GetAndTouchOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(expiry, "Expiry", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_GET_AND_TOUCH, opts.parentSpan().orElse(null)); long encodedExpiry = expiry.encode(environment.eventBus()); GetAndTouchRequest request = new GetAndTouchRequest( id, timeout, coreContext, collectionIdentifier, retryStrategy, encodedExpiry, span ); request.context().clientContext(opts.clientContext()); return request; } /** * Reads from all available replicas and the active node and returns the results as a list * of futures that might complete or fail. * * @param id the document id. * @return a list of results from the active and the replica. */ public CompletableFuture>> getAllReplicas(final String id) { return getAllReplicas(id, DEFAULT_GET_ALL_REPLICAS_OPTIONS); } /** * Reads from replicas or the active node based on the options and returns the results as a list * of futures that might complete or fail. * * @param id the document id. * @return a list of results from the active and the replica. */ public CompletableFuture>> getAllReplicas(final String id, final GetAllReplicasOptions options) { notNull(options, "GetAllReplicasOptions"); GetAllReplicasOptions.Built opts = options.build(); Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); return ReplicaHelper.getAllReplicasAsync( core, collectionIdentifier, id, opts.timeout().orElse(environment.timeoutConfig().kvTimeout()), opts.retryStrategy().orElse(environment().retryStrategy()), opts.clientContext(), opts.parentSpan().orElse(null), response -> GetReplicaResult.from(response, transcoder)); } /** * Reads all available replicas, and returns the first found. * * @param id the document id. * @return a future containing the first available replica. */ public CompletableFuture getAnyReplica(final String id) { return getAnyReplica(id, DEFAULT_GET_ANY_REPLICA_OPTIONS); } /** * Reads all available replicas, and returns the first found. * * @param id the document id. * @param options the custom options. * @return a future containing the first available replica. */ public CompletableFuture getAnyReplica(final String id, final GetAnyReplicaOptions options) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(options, "GetAnyReplicaOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); GetAnyReplicaOptions.Built opts = options.build(); Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); return ReplicaHelper.getAnyReplicaAsync( core, collectionIdentifier, id, opts.timeout().orElse(environment.timeoutConfig().kvTimeout()), opts.retryStrategy().orElse(environment().retryStrategy()), opts.clientContext(), opts.parentSpan().orElse(null), response -> GetReplicaResult.from(response, transcoder)); } /** * Checks if the given document ID exists on the active partition with default options. * * @param id the document ID * @return a {@link CompletableFuture} completing once loaded or failed. */ public CompletableFuture exists(final String id) { return exists(id, DEFAULT_EXISTS_OPTIONS); } /** * Checks if the given document ID exists on the active partition with custom options. * * @param id the document ID * @param options to modify the default behavior * @return a {@link CompletableFuture} completing once loaded or failed. */ public CompletableFuture exists(final String id, final ExistsOptions options) { return ExistsAccessor.exists(id, core, existsRequest(id, options)); } /** * Helper method to create the exists request from its options. * * @param id the document ID * @param options custom options to change the default behavior * @return the observe request used for exists. */ GetMetaRequest existsRequest(final String id, final ExistsOptions options) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(options, "ExistsOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); ExistsOptions.Built opts = options.build(); Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_EXISTS, opts.parentSpan().orElse(null)); GetMetaRequest request = new GetMetaRequest(id, timeout, coreContext, collectionIdentifier, retryStrategy, span); request.context().clientContext(opts.clientContext()); return request; } /** * Removes a Document from a collection with default options. * * @param id the id of the document to remove. * @return a {@link CompletableFuture} completing once removed or failed. */ public CompletableFuture remove(final String id) { return remove(id, DEFAULT_REMOVE_OPTIONS); } /** * Removes a Document from a collection with custom options. * * @param id the id of the document to remove. * @param options custom options to change the default behavior. * @return a {@link CompletableFuture} completing once removed or failed. */ public CompletableFuture remove(final String id, final RemoveOptions options) { notNull(options, "RemoveOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); RemoveOptions.Built opts = options.build(); return RemoveAccessor.remove(core, removeRequest(id, opts), id, opts.persistTo(), opts.replicateTo()); } /** * Helper method to create the remove request. * * @param id the id of the document to remove. * @param opts custom options to change the default behavior. * @return the remove request. */ RemoveRequest removeRequest(final String id, final RemoveOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); Duration timeout = decideKvTimeout(opts, environment.timeoutConfig()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); final RequestSpan span = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_REMOVE, opts.parentSpan().orElse(null)); RemoveRequest request = new RemoveRequest(id, opts.cas(), timeout, coreContext, collectionIdentifier, retryStrategy, opts.durabilityLevel(), span); request.context().clientContext(opts.clientContext()); return request; } /** * Inserts a full document which does not exist yet with default options. * * @param id the document id to insert. * @param content the document content to insert. * @return a {@link CompletableFuture} completing once inserted or failed. */ public CompletableFuture insert(final String id, Object content) { return insert(id, content, DEFAULT_INSERT_OPTIONS); } /** * Inserts a full document which does not exist yet with custom options. * * @param id the document id to insert. * @param content the document content to insert. * @param options custom options to customize the insert behavior. * @return a {@link CompletableFuture} completing once inserted or failed. */ public CompletableFuture insert(final String id, Object content, final InsertOptions options) { notNull(options, "InsertOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); InsertOptions.Built opts = options.build(); return InsertAccessor.insert(core, insertRequest(id, content, opts), id, opts.persistTo(), opts.replicateTo()); } /** * Helper method to generate the insert request. * * @param id the document id to insert. * @param content the document content to insert. * @param opts custom options to customize the insert behavior. * @return the insert request. */ InsertRequest insertRequest(final String id, final Object content, final InsertOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(content, "Content", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); Duration timeout = decideKvTimeout(opts, environment.timeoutConfig()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); final RequestSpan span = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_INSERT, opts.parentSpan().orElse(null)); final RequestSpan encodeSpan = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_ENCODING, span); long start = System.nanoTime(); Transcoder.EncodedValue encoded; try { encoded = transcoder.encode(content); } finally { encodeSpan.end(); } long end = System.nanoTime(); long expiry = opts.expiry().encode(environment.eventBus()); InsertRequest request = new InsertRequest(id, encoded.encoded(), expiry, encoded.flags(), timeout, coreContext, collectionIdentifier, retryStrategy, opts.durabilityLevel(), span); request.context() .clientContext(opts.clientContext()) .encodeLatency(end - start); return request; } /** * Upserts a full document which might or might not exist yet with default options. * * @param id the document id to upsert. * @param content the document content to upsert. * @return a {@link CompletableFuture} completing once upserted or failed. */ public CompletableFuture upsert(final String id, Object content) { return upsert(id, content, DEFAULT_UPSERT_OPTIONS); } /** * Upserts a full document which might or might not exist yet with custom options. * * @param id the document id to upsert. * @param content the document content to upsert. * @param options custom options to customize the upsert behavior. * @return a {@link CompletableFuture} completing once upserted or failed. */ public CompletableFuture upsert(final String id, Object content, final UpsertOptions options) { notNull(options, "UpsertOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); UpsertOptions.Built opts = options.build(); return UpsertAccessor.upsert(core, upsertRequest(id, content, opts), id, opts.persistTo(), opts.replicateTo()); } /** * Helper method to generate the upsert request. * * @param id the document id to upsert. * @param content the document content to upsert. * @param opts custom options to customize the upsert behavior. * @return the upsert request. */ UpsertRequest upsertRequest(final String id, final Object content, final UpsertOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(content, "Content", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); Duration timeout = decideKvTimeout(opts, environment.timeoutConfig()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); final RequestSpan span = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_UPSERT, opts.parentSpan().orElse(null)); final RequestSpan encodeSpan = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_ENCODING, span); long start = System.nanoTime(); Transcoder.EncodedValue encoded; try { encoded = transcoder.encode(content); } finally { encodeSpan.end(); } long end = System.nanoTime(); long expiry = opts.expiry().encode(environment.eventBus()); final UpsertRequest request = new UpsertRequest(id, encoded.encoded(), expiry, encoded.flags(), timeout, coreContext, collectionIdentifier, retryStrategy, opts.durabilityLevel(), span); request.context() .clientContext(opts.clientContext()) .encodeLatency(end - start); return request; } /** * Replaces a full document which already exists with default options. * * @param id the document id to replace. * @param content the document content to replace. * @return a {@link CompletableFuture} completing once replaced or failed. */ public CompletableFuture replace(final String id, Object content) { return replace(id, content, DEFAULT_REPLACE_OPTIONS); } /** * Replaces a full document which already exists with custom options. * * @param id the document id to replace. * @param content the document content to replace. * @param options custom options to customize the replace behavior. * @return a {@link CompletableFuture} completing once replaced or failed. */ public CompletableFuture replace(final String id, Object content, final ReplaceOptions options) { notNull(options, "ReplaceOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); ReplaceOptions.Built opts = options.build(); return ReplaceAccessor.replace(core, replaceRequest(id, content, opts), id, opts.persistTo(), opts.replicateTo()); } /** * Helper method to generate the replace request. * * @param id the document id to replace. * @param content the document content to replace. * @param opts custom options to customize the replace behavior. * @return the replace request. */ ReplaceRequest replaceRequest(final String id, final Object content, final ReplaceOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(content, "Content", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); Duration timeout = decideKvTimeout(opts, environment.timeoutConfig()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); Transcoder transcoder = opts.transcoder() == null ? environment.transcoder() : opts.transcoder(); final RequestSpan span = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_REPLACE, opts.parentSpan().orElse(null)); final RequestSpan encodeSpan = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_ENCODING, span); long start = System.nanoTime(); Transcoder.EncodedValue encoded; try { encoded = transcoder.encode(content); } finally { encodeSpan.end(); } long end = System.nanoTime(); long expiry = opts.expiry().encode(environment.eventBus()); ReplaceRequest request = new ReplaceRequest(id, encoded.encoded(), expiry, encoded.flags(), timeout, opts.cas(), coreContext, collectionIdentifier, retryStrategy, opts.durabilityLevel(), span); request.context() .clientContext(opts.clientContext()) .encodeLatency(end - start); return request; } /** * Updates the expiry of the document with the given id with default options. * * @param id the id of the document to update. * @param expiry the new expiry for the document. * @return a {@link MutationResult} once the operation completes. */ public CompletableFuture touch(final String id, final Duration expiry) { return touch(id, expiry, DEFAULT_TOUCH_OPTIONS); } /** * Updates the expiry of the document with the given id with custom options. * * @param id the id of the document to update. * @param expiry the new expiry for the document. * @param options the custom options. * @return a {@link MutationResult} once the operation completes. */ public CompletableFuture touch(final String id, final Duration expiry, final TouchOptions options) { notNull(expiry, "Expiry", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); return TouchAccessor.touch(core, touchRequest(id, Expiry.relative(expiry), options), id); } /** * Helper method to create the touch request. * * @param id the id of the document to update. * @param expiry the new expiry for the document. * @param options the custom options. * @return the touch request. */ TouchRequest touchRequest(final String id, final Expiry expiry, final TouchOptions options) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(expiry, "Expiry", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(options, "TouchOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); TouchOptions.Built opts = options.build(); Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_TOUCH, opts.parentSpan().orElse(null)); long encodedExpiry = expiry.encode(environment.eventBus()); TouchRequest request = new TouchRequest(timeout, coreContext, collectionIdentifier, retryStrategy, id, encodedExpiry, span); request.context().clientContext(opts.clientContext()); return request; } /** * Unlocks a document if it has been locked previously, with default options. * * @param id the id of the document. * @param cas the CAS value which is needed to unlock it. * @return the future which completes once a response has been received. */ public CompletableFuture unlock(final String id, final long cas) { return unlock(id, cas, DEFAULT_UNLOCK_OPTIONS); } /** * Unlocks a document if it has been locked previously, with custom options. * * @param id the id of the document. * @param cas the CAS value which is needed to unlock it. * @param options the options to customize. * @return the future which completes once a response has been received. */ public CompletableFuture unlock(final String id, final long cas, final UnlockOptions options) { return UnlockAccessor.unlock(id, core, unlockRequest(id, cas, options)); } /** * Helper method to create the unlock request. * * @param id the id of the document. * @param cas the CAS value which is needed to unlock it. * @param options the options to customize. * @return the unlock request. */ UnlockRequest unlockRequest(final String id, final long cas, final UnlockOptions options) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNull(options, "UnlockOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); if (cas == 0) { throw new InvalidArgumentException("CAS cannot be 0", null, ReducedKeyValueErrorContext.create(id, collectionIdentifier)); } UnlockOptions.Built opts = options.build(); Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_UNLOCK, opts.parentSpan().orElse(null)); UnlockRequest request = new UnlockRequest(timeout, coreContext, collectionIdentifier, retryStrategy, id, cas, span); request.context().clientContext(opts.clientContext()); return request; } /** * Performs lookups to document fragments with default options. * * @param id the outer document ID. * @param specs the spec which specifies the type of lookups to perform. * @return the {@link LookupInResult} once the lookup has been performed or failed. */ public CompletableFuture lookupIn(final String id, final List specs) { return lookupIn(id, specs, DEFAULT_LOOKUP_IN_OPTIONS); } /** * Performs lookups to document fragments with custom options. * * @param id the outer document ID. * @param specs the spec which specifies the type of lookups to perform. * @param options custom options to modify the lookup options. * @return the {@link LookupInResult} once the lookup has been performed or failed. */ public CompletableFuture lookupIn(final String id, final List specs, final LookupInOptions options) { notNull(options, "LookupInOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); LookupInOptions.Built opts = options.build(); final JsonSerializer serializer = opts.serializer() == null ? environment.jsonSerializer() : opts.serializer(); return LookupInAccessor.lookupInAccessor(core, lookupInRequest(id, specs, opts), serializer); } /** * Helper method to create the underlying lookup subdoc request. * * @param id the outer document ID. * @param specs the spec which specifies the type of lookups to perform. * @param opts custom options to modify the lookup options. * @return the subdoc lookup request. */ SubdocGetRequest lookupInRequest(final String id, final List specs, final LookupInOptions.Built opts) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNullOrEmpty(specs, "LookupInSpecs", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); ArrayList commands = new ArrayList<>(specs.size()); for (int i = 0; i < specs.size(); i ++) { LookupInSpec spec = specs.get(i); commands.add(spec.export(i)); } // xattrs come first commands.sort(Comparator.comparing(v -> !v.xattr())); Duration timeout = opts.timeout().orElse(environment.timeoutConfig().kvTimeout()); RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); byte flags = 0; if (opts.accessDeleted()) { flags |= SubdocMutateRequest.SUBDOC_DOC_FLAG_ACCESS_DELETED; } RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_LOOKUP_IN, opts.parentSpan().orElse(null)); SubdocGetRequest request = new SubdocGetRequest(timeout, coreContext, collectionIdentifier, retryStrategy, id, flags, commands, span); request.context().clientContext(opts.clientContext()); return request; } /** * Performs mutations to document fragments with default options. * * @param id the outer document ID. * @param specs the spec which specifies the type of mutations to perform. * @return the {@link MutateInResult} once the mutation has been performed or failed. */ public CompletableFuture mutateIn(final String id, final List specs) { return mutateIn(id, specs, DEFAULT_MUTATE_IN_OPTIONS); } /** * Performs mutations to document fragments with custom options. * * @param id the outer document ID. * @param specs the spec which specifies the type of mutations to perform. * @param options custom options to modify the mutation options. * @return the {@link MutateInResult} once the mutation has been performed or failed. */ public CompletableFuture mutateIn(final String id, final List specs, final MutateInOptions options) { notNull(options, "MutateInOptions", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); MutateInOptions.Built opts = options.build(); Duration timeout = decideKvTimeout(opts, environment.timeoutConfig()); return mutateInRequest(id, specs, opts, timeout) .thenCompose(request -> MutateInAccessor.mutateIn( core, request, id, opts.persistTo(), opts.replicateTo(), opts.storeSemantics() == StoreSemantics.INSERT, environment.jsonSerializer() )); } /** * Helper method to create the underlying subdoc mutate request. * * @param id the outer document ID. * @param specs the spec which specifies the type of mutations to perform. * @param opts custom options to modify the mutation options. * @return the subdoc mutate request. */ CompletableFuture mutateInRequest(final String id, final List specs, final MutateInOptions.Built opts, final Duration timeout) { notNullOrEmpty(id, "Id", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); notNullOrEmpty(specs, "MutateInSpecs", () -> ReducedKeyValueErrorContext.create(id, collectionIdentifier)); if (specs.isEmpty()) { throw SubdocMutateRequest.errIfNoCommands(ReducedKeyValueErrorContext.create(id, collectionIdentifier)); } else if (specs.size() > SubdocMutateRequest.SUBDOC_MAX_FIELDS) { throw SubdocMutateRequest.errIfTooManyCommands(ReducedKeyValueErrorContext.create(id, collectionIdentifier)); } final boolean requiresBucketConfig = opts.createAsDeleted(); CompletableFuture bucketConfigFuture; if (requiresBucketConfig) { bucketConfigFuture = BucketConfigUtil.waitForBucketConfig(core, bucketName(), timeout).toFuture(); } else { // Nothing will be using the bucket config so just provide null bucketConfigFuture = CompletableFuture.completedFuture(null); } return bucketConfigFuture.thenCompose(bucketConfig -> { RetryStrategy retryStrategy = opts.retryStrategy().orElse(environment.retryStrategy()); JsonSerializer serializer = opts.serializer() == null ? environment.jsonSerializer() : opts.serializer(); final RequestSpan span = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_MUTATE_IN, opts.parentSpan().orElse(null)); ArrayList commands = new ArrayList<>(specs.size()); final RequestSpan encodeSpan = environment .requestTracer() .requestSpan(TracingIdentifiers.SPAN_REQUEST_ENCODING, span); long start = System.nanoTime(); try { for (int i = 0; i < specs.size(); i++) { MutateInSpec spec = specs.get(i); commands.add(spec.encode(serializer, i)); } } finally { encodeSpan.end(); } long end = System.nanoTime(); // xattrs come first commands.sort(Comparator.comparing(v -> !v.xattr())); long expiry = opts.expiry().encode(environment.eventBus()); SubdocMutateRequest request = new SubdocMutateRequest(timeout, coreContext, collectionIdentifier, bucketConfig, retryStrategy, id, opts.storeSemantics() == StoreSemantics.INSERT, opts.storeSemantics() == StoreSemantics.UPSERT, opts.accessDeleted(), opts.createAsDeleted(), commands, expiry, opts.cas(), opts.durabilityLevel(), span ); request.context() .clientContext(opts.clientContext()) .encodeLatency(end - start); final CompletableFuture future = new CompletableFuture<>(); future.complete(request); return future; }); } /** * Helper method to decide if the user timeout, the kv timeout or the durable kv timeout should be used. * * @param opts the built opts from the command. * @param config the env timeout config to use if not overridden by the user. * @return the timeout to use for the op. */ @SuppressWarnings("unchecked") static Duration decideKvTimeout(CommonDurabilityOptions.BuiltCommonDurabilityOptions opts, TimeoutConfig config) { Optional userTimeout = opts.timeout(); if (userTimeout.isPresent()) { return userTimeout.get(); } boolean syncDurability = opts.durabilityLevel().isPresent() && ( opts.durabilityLevel().get() == DurabilityLevel.MAJORITY_AND_PERSIST_TO_ACTIVE || opts.durabilityLevel().get() == DurabilityLevel.PERSIST_TO_MAJORITY); boolean pollDurability = opts.persistTo() != PersistTo.NONE; if (syncDurability || pollDurability) { return config.kvDurableTimeout(); } else { return config.kvTimeout(); } } CollectionIdentifier collectionIdentifier() { return collectionIdentifier; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy