com.mongodb.internal.operation.CommandOperationHelper 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.Function;
import com.mongodb.MongoClientException;
import com.mongodb.MongoCommandException;
import com.mongodb.MongoConnectionPoolClearedException;
import com.mongodb.MongoException;
import com.mongodb.MongoNodeIsRecoveringException;
import com.mongodb.MongoNotPrimaryException;
import com.mongodb.MongoSecurityException;
import com.mongodb.MongoServerException;
import com.mongodb.MongoSocketException;
import com.mongodb.ReadPreference;
import com.mongodb.assertions.Assertions;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerDescription;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.async.function.AsyncCallbackSupplier;
import com.mongodb.internal.async.function.RetryState;
import com.mongodb.internal.async.function.RetryingAsyncCallbackSupplier;
import com.mongodb.internal.async.function.RetryingSyncSupplier;
import com.mongodb.internal.binding.AsyncConnectionSource;
import com.mongodb.internal.binding.AsyncReadBinding;
import com.mongodb.internal.binding.AsyncWriteBinding;
import com.mongodb.internal.binding.ConnectionSource;
import com.mongodb.internal.binding.ReadBinding;
import com.mongodb.internal.binding.WriteBinding;
import com.mongodb.internal.connection.AsyncConnection;
import com.mongodb.internal.connection.Connection;
import com.mongodb.internal.operation.OperationHelper.ResourceSupplierInternalException;
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.FieldNameValidator;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.Decoder;
import java.util.List;
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.async.ErrorHandlingResultCallback.errorHandlingCallback;
import static com.mongodb.internal.operation.OperationHelper.LOGGER;
import static com.mongodb.internal.operation.OperationHelper.canRetryRead;
import static com.mongodb.internal.operation.OperationHelper.canRetryWrite;
import static com.mongodb.internal.operation.OperationHelper.withAsyncSourceAndConnection;
import static com.mongodb.internal.operation.OperationHelper.withSourceAndConnection;
import static java.lang.String.format;
import static java.util.Arrays.asList;
@SuppressWarnings("overloads")
final class CommandOperationHelper {
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);
}
interface CommandWriteTransformerAsync {
/**
* Yield an appropriate result object for the input object.
*
* @param t the input object
* @return the function result
*/
@Nullable
R apply(T t, AsyncConnection connection);
}
interface CommandReadTransformerAsync {
/**
* Yield an appropriate result object for the input object.
*
* @param t the input object
* @return the function result
*/
@Nullable
R apply(T t, AsyncConnectionSource source, AsyncConnection connection);
}
static CommandWriteTransformer writeConcernErrorTransformer() {
return (result, connection) -> {
WriteConcernHelper.throwOnWriteConcernError(result, connection.getDescription().getServerAddress(),
connection.getDescription().getMaxWireVersion());
return null;
};
}
static CommandWriteTransformerAsync writeConcernErrorWriteTransformer() {
return (result, connection) -> {
WriteConcernHelper.throwOnWriteConcernError(result, connection.getDescription().getServerAddress(),
connection.getDescription().getMaxWireVersion());
return null;
};
}
static CommandWriteTransformerAsync writeConcernErrorTransformerAsync() {
return (result, connection) -> {
WriteConcernHelper.throwOnWriteConcernError(result, connection.getDescription().getServerAddress(),
connection.getDescription().getMaxWireVersion());
return null;
};
}
interface CommandCreator {
BsonDocument create(ServerDescription serverDescription, ConnectionDescription connectionDescription);
}
private static Throwable chooseRetryableReadException(
@Nullable final Throwable previouslyChosenException, final Throwable mostRecentAttemptException) {
assertFalse(mostRecentAttemptException instanceof ResourceSupplierInternalException);
if (previouslyChosenException == null
|| mostRecentAttemptException instanceof MongoSocketException
|| mostRecentAttemptException instanceof MongoServerException) {
return mostRecentAttemptException;
} else {
return previouslyChosenException;
}
}
static Throwable chooseRetryableWriteException(
@Nullable final Throwable previouslyChosenException, final Throwable mostRecentAttemptException) {
if (previouslyChosenException == null) {
if (mostRecentAttemptException instanceof ResourceSupplierInternalException) {
return mostRecentAttemptException.getCause();
}
return mostRecentAttemptException;
} else if (mostRecentAttemptException instanceof ResourceSupplierInternalException
|| (mostRecentAttemptException instanceof MongoException
&& ((MongoException) mostRecentAttemptException).hasErrorLabel(NO_WRITES_PERFORMED_ERROR_LABEL))) {
return previouslyChosenException;
} else {
return mostRecentAttemptException;
}
}
/* Read Binding Helpers */
static RetryState initialRetryState(final boolean retry) {
return new RetryState(retry ? RetryState.RETRIES : 0);
}
static Supplier decorateReadWithRetries(final RetryState retryState, final Supplier readFunction) {
return new RetryingSyncSupplier<>(retryState, CommandOperationHelper::chooseRetryableReadException,
CommandOperationHelper::shouldAttemptToRetryRead, readFunction);
}
static AsyncCallbackSupplier decorateReadWithRetries(final RetryState retryState,
final AsyncCallbackSupplier asyncReadFunction) {
return new RetryingAsyncCallbackSupplier<>(retryState, CommandOperationHelper::chooseRetryableReadException,
CommandOperationHelper::shouldAttemptToRetryRead, asyncReadFunction);
}
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 = initialRetryState(retryReads);
Supplier read = decorateReadWithRetries(retryState, () -> {
logRetryExecute(retryState);
return 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();
}
@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);
logRetryExecute(retryState);
return transformer.apply(assertNotNull(connection.command(database, command, new NoOpFieldNameValidator(),
source.getReadPreference(), decoder, binding.getSessionContext(), binding.getServerApi(), binding.getRequestContext())),
source, connection);
}
/* Write Binding Helpers */
@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, source.getSessionContext(),
source.getServerApi(), binding.getRequestContext())), 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.getSessionContext(), binding.getServerApi(), binding.getRequestContext())), connection);
}
/* Async Read Binding Helpers */
static void executeRetryableReadAsync(
final AsyncReadBinding binding,
final String database,
final CommandCreator commandCreator,
final Decoder decoder,
final CommandReadTransformerAsync transformer,
final boolean retryReads,
final SingleResultCallback callback) {
executeRetryableReadAsync(binding, binding::getReadConnectionSource, database, commandCreator, decoder, transformer, retryReads,
callback);
}
static void executeRetryableReadAsync(
final AsyncReadBinding binding,
final AsyncCallbackSupplier sourceAsyncSupplier,
final String database,
final CommandCreator commandCreator,
final Decoder decoder,
final CommandReadTransformerAsync transformer,
final boolean retryReads,
final SingleResultCallback callback) {
RetryState retryState = initialRetryState(retryReads);
binding.retain();
AsyncCallbackSupplier asyncRead = CommandOperationHelper.decorateReadWithRetries(retryState, funcCallback -> {
logRetryExecute(retryState);
withAsyncSourceAndConnection(sourceAsyncSupplier, false, funcCallback,
(source, connection, releasingCallback) -> {
if (retryState.breakAndCompleteIfRetryAnd(() -> !canRetryRead(source.getServerDescription(),
binding.getSessionContext()), releasingCallback)) {
return;
}
createReadCommandAndExecuteAsync(retryState, binding, source, database, commandCreator, decoder, transformer,
connection, releasingCallback);
});
}).whenComplete(binding::release);
asyncRead.get(errorHandlingCallback(callback, LOGGER));
}
static void createReadCommandAndExecuteAsync(
final RetryState retryState,
final AsyncReadBinding binding,
final AsyncConnectionSource source,
final String database,
final CommandCreator commandCreator,
final Decoder decoder,
final CommandReadTransformerAsync transformer,
final AsyncConnection connection,
final SingleResultCallback callback) {
BsonDocument command;
try {
command = commandCreator.create(source.getServerDescription(), connection.getDescription());
retryState.attach(AttachmentKeys.commandDescriptionSupplier(), command::getFirstKey, false);
logRetryExecute(retryState);
} catch (IllegalArgumentException e) {
callback.onResult(null, e);
return;
}
connection.commandAsync(database, command, new NoOpFieldNameValidator(), source.getReadPreference(), decoder,
binding.getSessionContext(), binding.getServerApi(), binding.getRequestContext(),
transformingReadCallback(transformer, source, connection, callback));
}
private static SingleResultCallback transformingReadCallback(final CommandReadTransformerAsync transformer,
final AsyncConnectionSource source, final AsyncConnection connection, final SingleResultCallback callback) {
return (result, t) -> {
if (t != null) {
callback.onResult(null, t);
} else {
R transformedResult;
try {
transformedResult = transformer.apply(result, source, connection);
} catch (Throwable e) {
callback.onResult(null, e);
return;
}
callback.onResult(transformedResult, null);
}
};
}
private static SingleResultCallback transformingWriteCallback(final CommandWriteTransformerAsync transformer,
final AsyncConnection connection, final SingleResultCallback callback) {
return (result, t) -> {
if (t != null) {
callback.onResult(null, t);
} else {
R transformedResult;
try {
transformedResult = transformer.apply(result, connection);
} catch (Throwable e) {
callback.onResult(null, e);
return;
}
callback.onResult(transformedResult, null);
}
};
}
/* Async Write Binding Helpers */
static void executeCommandAsync(final AsyncWriteBinding binding,
final String database,
final BsonDocument command,
final AsyncConnection connection,
final CommandWriteTransformerAsync transformer,
final SingleResultCallback callback) {
notNull("binding", binding);
SingleResultCallback addingRetryableLabelCallback = addingRetryableLabelCallback(callback,
connection.getDescription().getMaxWireVersion());
connection.commandAsync(database, command, new NoOpFieldNameValidator(), primary(), new BsonDocumentCodec(),
binding.getSessionContext(), binding.getServerApi(), binding.getRequestContext(),
transformingWriteCallback(transformer, connection, addingRetryableLabelCallback));
}
static Supplier decorateWriteWithRetries(final RetryState retryState, final Supplier writeFunction) {
return new RetryingSyncSupplier<>(retryState, CommandOperationHelper::chooseRetryableWriteException,
CommandOperationHelper::shouldAttemptToRetryWrite, writeFunction);
}
static AsyncCallbackSupplier decorateWriteWithRetries(final RetryState retryState,
final AsyncCallbackSupplier asyncWriteFunction) {
return new RetryingAsyncCallbackSupplier<>(retryState, CommandOperationHelper::chooseRetryableWriteException,
CommandOperationHelper::shouldAttemptToRetryWrite, asyncWriteFunction);
}
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 Function retryCommandModifier) {
RetryState retryState = initialRetryState(true);
Supplier retryingWrite = decorateWriteWithRetries(retryState, () -> {
logRetryExecute(retryState);
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(source.getServerDescription(), 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(), isRetryWritesEnabled(command), true)
.attach(AttachmentKeys.commandDescriptionSupplier(), command::getFirstKey, true)
.attach(AttachmentKeys.command(), command, false);
logRetryExecute(retryState);
return transformer.apply(connection.command(database, command, fieldNameValidator, readPreference,
commandResultDecoder, binding.getSessionContext(), binding.getServerApi(), binding.getRequestContext()),
connection);
} catch (MongoException e) {
if (!firstAttempt) {
addRetryableWriteErrorLabel(e, maxWireVersion);
}
throw e;
}
});
});
try {
return retryingWrite.get();
} catch (MongoException e) {
throw transformWriteException(e);
}
}
static void executeRetryableWriteAsync(
final AsyncWriteBinding binding,
final String database,
@Nullable final ReadPreference readPreference,
final FieldNameValidator fieldNameValidator,
final Decoder commandResultDecoder,
final CommandCreator commandCreator,
final CommandWriteTransformerAsync transformer,
final Function retryCommandModifier,
final SingleResultCallback callback) {
RetryState retryState = initialRetryState(true);
binding.retain();
AsyncCallbackSupplier asyncWrite = CommandOperationHelper.decorateWriteWithRetries(retryState, funcCallback -> {
logRetryExecute(retryState);
boolean firstAttempt = retryState.isFirstAttempt();
if (!firstAttempt && binding.getSessionContext().hasActiveTransaction()) {
binding.getSessionContext().clearTransactionContext();
}
withAsyncSourceAndConnection(binding::getWriteConnectionSource, true, funcCallback,
(source, connection, releasingCallback) -> {
int maxWireVersion = connection.getDescription().getMaxWireVersion();
SingleResultCallback addingRetryableLabelCallback = firstAttempt
? releasingCallback
: addingRetryableLabelCallback(releasingCallback, maxWireVersion);
if (retryState.breakAndCompleteIfRetryAnd(() -> !canRetryWrite(source.getServerDescription(), connection.getDescription(),
binding.getSessionContext()), addingRetryableLabelCallback)) {
return;
}
BsonDocument command;
try {
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(), isRetryWritesEnabled(command), true)
.attach(AttachmentKeys.commandDescriptionSupplier(), command::getFirstKey, true)
.attach(AttachmentKeys.command(), command, false);
logRetryExecute(retryState);
} catch (Throwable t) {
addingRetryableLabelCallback.onResult(null, t);
return;
}
connection.commandAsync(database, command, fieldNameValidator, readPreference,
commandResultDecoder, binding.getSessionContext(),
binding.getServerApi(), binding.getRequestContext(),
transformingWriteCallback(transformer, connection, addingRetryableLabelCallback));
});
}).whenComplete(binding::release);
asyncWrite.get(exceptionTransformingCallback(errorHandlingCallback(callback, LOGGER)));
}
private static SingleResultCallback addingRetryableLabelCallback(final SingleResultCallback callback,
final int maxWireVersion) {
return (result, t) -> {
if (t != null) {
if (t instanceof MongoException) {
addRetryableWriteErrorLabel((MongoException) t, maxWireVersion);
}
callback.onResult(null, t);
} else {
callback.onResult(result, null);
}
};
}
private static final List RETRYABLE_ERROR_CODES = asList(6, 7, 89, 91, 189, 262, 9001, 13436, 13435, 11602, 11600, 10107);
static boolean isRetryableException(final Throwable t) {
if (!(t instanceof MongoException)) {
return false;
}
if (t instanceof MongoSocketException || t instanceof MongoNotPrimaryException || t instanceof MongoNodeIsRecoveringException
|| t instanceof MongoConnectionPoolClearedException) {
return true;
}
return RETRYABLE_ERROR_CODES.contains(((MongoException) t).getCode());
}
/* Misc operation helpers */
static void rethrowIfNotNamespaceError(final MongoCommandException e) {
rethrowIfNotNamespaceError(e, null);
}
@Nullable
static T rethrowIfNotNamespaceError(final MongoCommandException e, @Nullable final T defaultValue) {
if (!isNamespaceError(e)) {
throw e;
}
return defaultValue;
}
static boolean isNamespaceError(final Throwable t) {
if (t instanceof MongoCommandException) {
MongoCommandException e = (MongoCommandException) t;
return (e.getErrorMessage().contains("ns not found") || e.getErrorCode() == 26);
} else {
return false;
}
}
private static boolean shouldAttemptToRetryRead(final RetryState retryState, final Throwable attemptFailure) {
assertFalse(attemptFailure instanceof ResourceSupplierInternalException);
boolean decision = isRetryableException(attemptFailure)
|| (attemptFailure instanceof MongoSecurityException
&& attemptFailure.getCause() != null && isRetryableException(attemptFailure.getCause()));
if (!decision) {
logUnableToRetry(retryState.attachment(AttachmentKeys.commandDescriptionSupplier()).orElse(null), attemptFailure);
}
return decision;
}
static boolean shouldAttemptToRetryWrite(final RetryState retryState, final Throwable attemptFailure) {
Throwable failure = attemptFailure instanceof ResourceSupplierInternalException ? attemptFailure.getCause() : attemptFailure;
boolean decision = false;
MongoException exceptionRetryableRegardlessOfCommand = null;
if (failure instanceof MongoConnectionPoolClearedException
|| (failure instanceof MongoSecurityException && failure.getCause() != null && isRetryableException(failure.getCause()))) {
decision = true;
exceptionRetryableRegardlessOfCommand = (MongoException) failure;
}
if (retryState.attachment(AttachmentKeys.retryableCommandFlag()).orElse(false)) {
if (exceptionRetryableRegardlessOfCommand != null) {
/* We are going to retry even if `retryableCommand` is false,
* but we add the retryable label only if `retryableCommand` is true. */
exceptionRetryableRegardlessOfCommand.addLabel(RETRYABLE_WRITE_ERROR_LABEL);
} else if (decideRetryableAndAddRetryableWriteErrorLabel(failure, retryState.attachment(AttachmentKeys.maxWireVersion())
.orElse(null))) {
decision = true;
} else {
logUnableToRetry(retryState.attachment(AttachmentKeys.commandDescriptionSupplier()).orElse(null), failure);
}
}
return decision;
}
private static boolean isRetryWritesEnabled(@Nullable final BsonDocument command) {
return (command != null && (command.containsKey("txnNumber")
|| command.getFirstKey().equals("commitTransaction") || command.getFirstKey().equals("abortTransaction")));
}
static final String RETRYABLE_WRITE_ERROR_LABEL = "RetryableWriteError";
private static final String NO_WRITES_PERFORMED_ERROR_LABEL = "NoWritesPerformed";
private static boolean decideRetryableAndAddRetryableWriteErrorLabel(final Throwable t, @Nullable final Integer maxWireVersion) {
if (!(t instanceof MongoException)) {
return false;
}
MongoException exception = (MongoException) t;
if (maxWireVersion != null) {
addRetryableWriteErrorLabel(exception, maxWireVersion);
}
return exception.hasErrorLabel(RETRYABLE_WRITE_ERROR_LABEL);
}
static void addRetryableWriteErrorLabel(final MongoException exception, final int maxWireVersion) {
if (maxWireVersion >= 9 && exception instanceof MongoSocketException) {
exception.addLabel(RETRYABLE_WRITE_ERROR_LABEL);
} else if (maxWireVersion < 9 && isRetryableException(exception)) {
exception.addLabel(RETRYABLE_WRITE_ERROR_LABEL);
}
}
static void logRetryExecute(final RetryState retryState) {
if (LOGGER.isDebugEnabled() && !retryState.isFirstAttempt()) {
String commandDescription = retryState.attachment(AttachmentKeys.commandDescriptionSupplier()).map(Supplier::get).orElse(null);
Throwable exception = retryState.exception().orElseThrow(Assertions::fail);
int oneBasedAttempt = retryState.attempt() + 1;
LOGGER.debug(commandDescription == null
? format("Retrying an operation due to the error \"%s\"; attempt #%d", exception, oneBasedAttempt)
: format("Retrying the operation %s due to the error \"%s\"; attempt #%d",
commandDescription, exception, oneBasedAttempt));
}
}
private static void logUnableToRetry(@Nullable final Supplier commandDescriptionSupplier, final Throwable originalError) {
if (LOGGER.isDebugEnabled()) {
String commandDescription = commandDescriptionSupplier == null ? null : commandDescriptionSupplier.get();
LOGGER.debug(commandDescription == null
? format("Unable to retry an operation due to the error \"%s\"", originalError)
: format("Unable to retry the operation %s due to the error \"%s\"", commandDescription, originalError));
}
}
static MongoException transformWriteException(final MongoException exception) {
if (exception.getCode() == 20 && exception.getMessage().contains("Transaction numbers")) {
MongoException clientException = new MongoClientException("This MongoDB deployment does not support retryable writes. "
+ "Please add retryWrites=false to your connection string.", exception);
for (final String errorLabel : exception.getErrorLabels()) {
clientException.addLabel(errorLabel);
}
return clientException;
}
return exception;
}
static SingleResultCallback exceptionTransformingCallback(final SingleResultCallback callback) {
return (result, t) -> {
if (t != null) {
if (t instanceof MongoException) {
callback.onResult(null, transformWriteException((MongoException) t));
} else {
callback.onResult(null, t);
}
} else {
callback.onResult(result, null);
}
};
}
private CommandOperationHelper() {
}
}