com.mongodb.internal.operation.SyncOperationHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongodb-driver-core Show documentation
Show all versions of mongodb-driver-core Show documentation
The Java operations layer for the MongoDB Java Driver. Third parties can ' +
'wrap this layer to provide custom higher-level APIs
/*
* 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() {
}
}