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

com.mongodb.internal.operation.SyncOperationHelper Maven / Gradle / Ivy

Go to download

The Java operations layer for the MongoDB Java Driver. Third parties can ' + 'wrap this layer to provide custom higher-level APIs

There is a newer version: 5.3.0-beta0
Show newest version
/*
 * Copyright 2008-present MongoDB, 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.mongodb.internal.operation;

import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.async.function.AsyncCallbackBiFunction;
import com.mongodb.internal.async.function.AsyncCallbackFunction;
import com.mongodb.internal.async.function.AsyncCallbackSupplier;
import com.mongodb.internal.async.function.RetryState;
import com.mongodb.internal.async.function.RetryingSyncSupplier;
import com.mongodb.internal.binding.ConnectionSource;
import com.mongodb.internal.binding.ReadBinding;
import com.mongodb.internal.binding.ReferenceCounted;
import com.mongodb.internal.binding.WriteBinding;
import com.mongodb.internal.connection.Connection;
import com.mongodb.internal.connection.OperationContext;
import com.mongodb.internal.connection.QueryResult;
import com.mongodb.internal.operation.retry.AttachmentKeys;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import com.mongodb.lang.Nullable;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.FieldNameValidator;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.Decoder;

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.mongodb.ReadPreference.primary;
import static com.mongodb.assertions.Assertions.assertFalse;
import static com.mongodb.assertions.Assertions.assertNotNull;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static com.mongodb.internal.operation.CommandOperationHelper.CommandCreator;
import static com.mongodb.internal.operation.CommandOperationHelper.logRetryExecute;
import static com.mongodb.internal.operation.OperationHelper.ResourceSupplierInternalException;
import static com.mongodb.internal.operation.OperationHelper.canRetryRead;
import static com.mongodb.internal.operation.OperationHelper.canRetryWrite;
import static com.mongodb.internal.operation.OperationHelper.cursorDocumentToQueryResult;
import static com.mongodb.internal.operation.WriteConcernHelper.throwOnWriteConcernError;

final class SyncOperationHelper {

    interface CallableWithConnection {
        T call(Connection connection);
    }

    interface CallableWithSource {
        T call(ConnectionSource source);
    }

    interface CommandReadTransformer {

        /**
         * Yield an appropriate result object for the input object.
         *
         * @param t the input object
         * @return the function result
         */
        @Nullable
        R apply(T t, ConnectionSource source, Connection connection);
    }

    interface CommandWriteTransformer {

        /**
         * Yield an appropriate result object for the input object.
         *
         * @param t the input object
         * @return the function result
         */
        @Nullable
        R apply(T t, Connection connection);
    }

    static  T withReadConnectionSource(final ReadBinding binding, final CallableWithSource callable) {
        ConnectionSource source = binding.getReadConnectionSource();
        try {
            return callable.call(source);
        } finally {
            source.release();
        }
    }

    static  T withConnection(final WriteBinding binding, final CallableWithConnection callable) {
        ConnectionSource source = binding.getWriteConnectionSource();
        try {
            return withConnectionSource(source, callable);
        } finally {
            source.release();
        }
    }

    /**
     * Gets a {@link ConnectionSource} and a {@link Connection} from the {@code sourceSupplier} and executes the {@code function} with them.
     * Guarantees to {@linkplain ReferenceCounted#release() release} the source and the connection after completion of the {@code function}.
     *
     * @param wrapConnectionSourceException See {@link #withSuppliedResource(Supplier, boolean, Function)}.
     * @see #withSuppliedResource(Supplier, boolean, Function)
     * @see AsyncOperationHelper#withAsyncSourceAndConnection(AsyncCallbackSupplier, boolean, SingleResultCallback, AsyncCallbackBiFunction)
     */
    static  R withSourceAndConnection(final Supplier sourceSupplier,
            final boolean wrapConnectionSourceException,
            final BiFunction function) throws ResourceSupplierInternalException {
        return withSuppliedResource(sourceSupplier, wrapConnectionSourceException, source ->
                withSuppliedResource(source::getConnection, wrapConnectionSourceException, connection ->
                        function.apply(source, connection)));
    }

    /**
     * Gets a {@link ReferenceCounted} resource from the {@code resourceSupplier} and applies the {@code function} to it.
     * Guarantees to {@linkplain ReferenceCounted#release() release} the resource after completion of the {@code function}.
     *
     * @param wrapSupplierException If {@code true} and {@code resourceSupplier} completes abruptly, then the exception is wrapped
     * into {@link OperationHelper.ResourceSupplierInternalException}, such that it can be accessed
     * via {@link OperationHelper.ResourceSupplierInternalException#getCause()}.
     * @see AsyncOperationHelper#withAsyncSuppliedResource(AsyncCallbackSupplier, boolean, SingleResultCallback, AsyncCallbackFunction)
     */
    static  R withSuppliedResource(final Supplier resourceSupplier,
            final boolean wrapSupplierException, final Function function) throws OperationHelper.ResourceSupplierInternalException {
        T resource = null;
        try {
            try {
                resource = resourceSupplier.get();
            } catch (Exception supplierException) {
                if (wrapSupplierException) {
                    throw new ResourceSupplierInternalException(supplierException);
                } else {
                    throw supplierException;
                }
            }
            return function.apply(resource);
        } finally {
            if (resource != null) {
                resource.release();
            }
        }
    }

    private static  T withConnectionSource(final ConnectionSource source, final CallableWithConnection callable) {
        Connection connection = source.getConnection();
        try {
            return callable.call(connection);
        } finally {
            connection.release();
        }
    }

    static  T executeRetryableRead(
            final ReadBinding binding,
            final String database,
            final CommandCreator commandCreator,
            final Decoder decoder,
            final CommandReadTransformer transformer,
            final boolean retryReads) {
        return executeRetryableRead(binding, binding::getReadConnectionSource, database, commandCreator, decoder, transformer, retryReads);
    }

    static  T executeRetryableRead(
            final ReadBinding binding,
            final Supplier readConnectionSourceSupplier,
            final String database,
            final CommandCreator commandCreator,
            final Decoder decoder,
            final CommandReadTransformer transformer,
            final boolean retryReads) {
        RetryState retryState = CommandOperationHelper.initialRetryState(retryReads);
        Supplier read = decorateReadWithRetries(retryState, binding.getOperationContext(), () ->
                withSourceAndConnection(readConnectionSourceSupplier, false, (source, connection) -> {
                    retryState.breakAndThrowIfRetryAnd(() -> !canRetryRead(source.getServerDescription(), binding.getSessionContext()));
                    return createReadCommandAndExecute(retryState, binding, source, database, commandCreator, decoder, transformer, connection);
                })
        );
        return read.get();
    }

    @VisibleForTesting(otherwise = PRIVATE)
    static  T executeCommand(final WriteBinding binding, final String database, final BsonDocument command,
                                   final Decoder decoder, final CommandWriteTransformer transformer) {
        return withSourceAndConnection(binding::getWriteConnectionSource, false, (source, connection) ->
                transformer.apply(assertNotNull(
                        connection.command(database, command, new NoOpFieldNameValidator(), primary(), decoder, binding)), connection));
    }

    @Nullable
    static  T executeCommand(final WriteBinding binding, final String database, final BsonDocument command,
                                final Connection connection, final CommandWriteTransformer transformer) {
        notNull("binding", binding);
        return transformer.apply(assertNotNull(
                connection.command(database, command, new NoOpFieldNameValidator(), primary(), new BsonDocumentCodec(), binding)),
                connection);
    }

    static  R executeRetryableWrite(
            final WriteBinding binding,
            final String database,
            @Nullable final ReadPreference readPreference,
            final FieldNameValidator fieldNameValidator,
            final Decoder commandResultDecoder,
            final CommandCreator commandCreator,
            final CommandWriteTransformer transformer,
            final com.mongodb.Function retryCommandModifier) {
        RetryState retryState = CommandOperationHelper.initialRetryState(true);
        Supplier retryingWrite = decorateWriteWithRetries(retryState, binding.getOperationContext(), () -> {
            boolean firstAttempt = retryState.isFirstAttempt();
            if (!firstAttempt && binding.getSessionContext().hasActiveTransaction()) {
                binding.getSessionContext().clearTransactionContext();
            }
            return withSourceAndConnection(binding::getWriteConnectionSource, true, (source, connection) -> {
                int maxWireVersion = connection.getDescription().getMaxWireVersion();
                try {
                    retryState.breakAndThrowIfRetryAnd(() -> !canRetryWrite(connection.getDescription(), binding.getSessionContext()));
                    BsonDocument command = retryState.attachment(AttachmentKeys.command())
                            .map(previousAttemptCommand -> {
                                assertFalse(firstAttempt);
                                return retryCommandModifier.apply(previousAttemptCommand);
                            }).orElseGet(() -> commandCreator.create(source.getServerDescription(), connection.getDescription()));
                    // attach `maxWireVersion`, `retryableCommandFlag` ASAP because they are used to check whether we should retry
                    retryState.attach(AttachmentKeys.maxWireVersion(), maxWireVersion, true)
                            .attach(AttachmentKeys.retryableCommandFlag(), CommandOperationHelper.isRetryWritesEnabled(command), true)
                            .attach(AttachmentKeys.commandDescriptionSupplier(), command::getFirstKey, false)
                            .attach(AttachmentKeys.command(), command, false);
                    return transformer.apply(assertNotNull(connection.command(database, command, fieldNameValidator, readPreference,
                                    commandResultDecoder, binding)),
                            connection);
                } catch (MongoException e) {
                    if (!firstAttempt) {
                        CommandOperationHelper.addRetryableWriteErrorLabel(e, maxWireVersion);
                    }
                    throw e;
                }
            });
        });
        try {
            return retryingWrite.get();
        } catch (MongoException e) {
            throw CommandOperationHelper.transformWriteException(e);
        }
    }

    @Nullable
    static  T createReadCommandAndExecute(
            final RetryState retryState,
            final ReadBinding binding,
            final ConnectionSource source,
            final String database,
            final CommandCreator commandCreator,
            final Decoder decoder,
            final CommandReadTransformer transformer,
            final Connection connection) {
        BsonDocument command = commandCreator.create(source.getServerDescription(), connection.getDescription());
        retryState.attach(AttachmentKeys.commandDescriptionSupplier(), command::getFirstKey, false);
        return transformer.apply(assertNotNull(connection.command(database, command, new NoOpFieldNameValidator(),
                source.getReadPreference(), decoder, binding)), source, connection);
    }


    static  Supplier decorateWriteWithRetries(final RetryState retryState,
            final OperationContext operationContext, final Supplier writeFunction) {
        return new RetryingSyncSupplier<>(retryState, CommandOperationHelper::chooseRetryableWriteException,
                CommandOperationHelper::shouldAttemptToRetryWrite, () -> {
            logRetryExecute(retryState, operationContext);
            return writeFunction.get();
        });
    }

    static  Supplier decorateReadWithRetries(final RetryState retryState, final OperationContext operationContext,
            final Supplier readFunction) {
        return new RetryingSyncSupplier<>(retryState, CommandOperationHelper::chooseRetryableReadException,
                CommandOperationHelper::shouldAttemptToRetryRead, () -> {
            logRetryExecute(retryState, operationContext);
            return readFunction.get();
        });
    }


    static CommandWriteTransformer writeConcernErrorTransformer() {
        return (result, connection) -> {
            assertNotNull(result);
            throwOnWriteConcernError(result, connection.getDescription().getServerAddress(),
                    connection.getDescription().getMaxWireVersion());
            return null;
        };
    }

    static  BatchCursor cursorDocumentToBatchCursor(final BsonDocument cursorDocument, final Decoder decoder,
            final BsonValue comment, final ConnectionSource source, final Connection connection, final int batchSize) {
        return new QueryBatchCursor<>(cursorDocumentToQueryResult(cursorDocument, source.getServerDescription().getAddress()),
                0, batchSize, 0, decoder, comment, source, connection);
    }

    static  QueryResult getMoreCursorDocumentToQueryResult(final BsonDocument cursorDocument, final ServerAddress serverAddress) {
        return cursorDocumentToQueryResult(cursorDocument, serverAddress, "nextBatch");
    }

    private SyncOperationHelper() {
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy