Skip to content

Commit

Permalink
Refine exception translation for client session exceptions.
Browse files Browse the repository at this point in the history
Closes #3148
See #2939
Original Pull Request: #604
  • Loading branch information
christophstrobl authored and mp911de committed Sep 4, 2024
1 parent d04e76f commit dced760
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* https://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 org.springframework.data.mongodb;

import org.springframework.dao.TransientDataAccessException;
import org.springframework.lang.Nullable;

/**
* {@link TransientDataAccessException} specific to MongoDB {@link com.mongodb.session.ClientSession} related data
* access failures such as reading data using an already closed session.
*
* @author Christoph Strobl
* @since 3.3
*/
public class TransientClientSessionException extends TransientMongoDbException {

/**
* Constructor for {@link TransientClientSessionException}.
*
* @param msg the detail message. Can be {@literal null}.
* @param cause the root cause. Can be {@literal null}.
*/
public TransientClientSessionException(@Nullable String msg, @Nullable Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* https://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 org.springframework.data.mongodb;

import org.springframework.dao.TransientDataAccessException;
import org.springframework.lang.Nullable;

/**
* Root of the hierarchy of MongoDB specific data access exceptions that are considered transient such as
* {@link com.mongodb.MongoException MongoExceptions} carrying {@link com.mongodb.MongoException#hasErrorLabel(String)
* specific labels}.
*
* @author Christoph Strobl
* @since 3.3
*/
public class TransientMongoDbException extends TransientDataAccessException {

/**
* Constructor for {@link TransientMongoDbException}.
*
* @param msg the detail message. Can be {@literal null}.
* @param cause the root cause. Can be {@literal null}.
*/
public TransientMongoDbException(String msg, @Nullable Throwable cause) {
super(msg, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.PermissionDeniedDataAccessException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.ClientSessionException;
import org.springframework.data.mongodb.MongoTransactionException;
import org.springframework.data.mongodb.TransientClientSessionException;
import org.springframework.data.mongodb.TransientMongoDbException;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.util.MongoDbErrorCodes;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -65,9 +67,26 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator

private static final Set<String> SECURITY_EXCEPTIONS = Set.of("MongoCryptException");

@Override
@Nullable
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {

DataAccessException translatedException = doTranslateException(ex);
if (translatedException == null) {
return null;
}

// Translated exceptions that per se are not be recoverable (eg. WriteConflicts), might still be transient inside a
// transaction. Let's wrap those.
return (isTransientFailure(ex) && !(translatedException instanceof TransientDataAccessException))
? new TransientMongoDbException(ex.getMessage(), translatedException)
: translatedException;

}

@Nullable
DataAccessException doTranslateException(RuntimeException ex) {

// Check for well-known MongoException subclasses.

if (ex instanceof BsonInvalidOperationException) {
Expand All @@ -94,13 +113,13 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {

if (DATA_INTEGRITY_EXCEPTIONS.contains(exception)) {

if (ex instanceof MongoServerException mse) {
if (mse.getCode() == 11000) {
if (ex instanceof MongoServerException) {
if (MongoDbErrorCodes.isDataDuplicateKeyError(ex)) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
if (ex instanceof MongoBulkWriteException bulkException) {
for (BulkWriteError x : bulkException.getWriteErrors()) {
if (x.getCode() == 11000) {
for (BulkWriteError writeError : bulkException.getWriteErrors()) {
if (MongoDbErrorCodes.isDuplicateKeyCode(writeError.getCode())) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
}
Expand All @@ -115,20 +134,27 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {

int code = mongoException.getCode();

if (MongoDbErrorCodes.isDuplicateKeyCode(code)) {
if (MongoDbErrorCodes.isDuplicateKeyError(mongoException)) {
return new DuplicateKeyException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isDataAccessResourceFailureCode(code)) {
}
if (MongoDbErrorCodes.isDataAccessResourceError(mongoException)) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isInvalidDataAccessApiUsageCode(code) || code == 10003 || code == 12001
|| code == 12010 || code == 12011 || code == 12012) {
}
if (MongoDbErrorCodes.isInvalidDataAccessApiUsageError(mongoException) || code == 12001 || code == 12010
|| code == 12011 || code == 12012) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) {
}
if (MongoDbErrorCodes.isPermissionDeniedError(mongoException)) {
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isClientSessionFailureCode(code)) {
return new ClientSessionException(ex.getMessage(), ex);
} else if (MongoDbErrorCodes.isTransactionFailureCode(code)) {
return new MongoTransactionException(ex.getMessage(), ex);
} else if(ex.getCause() != null && SECURITY_EXCEPTIONS.contains(ClassUtils.getShortName(ex.getCause().getClass()))) {
}
if (MongoDbErrorCodes.isDataIntegrityViolationError(mongoException)) {
return new DataIntegrityViolationException(mongoException.getMessage(), mongoException);
}
if (MongoDbErrorCodes.isClientSessionFailure(mongoException)) {
return isTransientFailure(mongoException) ? new TransientClientSessionException(ex.getMessage(), ex)
: new ClientSessionException(ex.getMessage(), ex);
}
if (ex.getCause() != null && SECURITY_EXCEPTIONS.contains(ClassUtils.getShortName(ex.getCause().getClass()))) {
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
}

Expand All @@ -150,4 +176,25 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
// that translation should not occur.
return null;
}

/**
* Check if a given exception holds an error label indicating a transient failure.
*
* @param e
* @return {@literal true} if the given {@link Exception} is a {@link MongoException} holding one of the transient
* exception error labels.
* @see MongoException#hasErrorLabel(String)
* @since 3.3
*/
public static boolean isTransientFailure(Exception e) {

if (!(e instanceof MongoException)) {
return false;
}

MongoException mongoException = (MongoException) e;

return mongoException.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)
|| mongoException.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.MappingContextEvent;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
Expand Down Expand Up @@ -152,7 +151,7 @@ void createIndex(IndexDefinitionHolder indexDefinition) {
IndexOperations indexOperations = indexOperationsProvider.indexOps(indexDefinition.getCollection());
indexOperations.ensureIndex(indexDefinition);

} catch (UncategorizedMongoDbException ex) {
} catch (DataIntegrityViolationException ex) {

if (ex.getCause() instanceof MongoException mongoException
&& MongoDbErrorCodes.isDataIntegrityViolationCode(mongoException.getCode())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
Expand Down Expand Up @@ -111,7 +111,7 @@ public void onCreation(PartTreeMongoQuery query) {
MongoEntityMetadata<?> metadata = query.getQueryMethod().getEntityInformation();
try {
indexOperationsProvider.indexOps(metadata.getCollectionName(), metadata.getJavaType()).ensureIndex(index);
} catch (UncategorizedMongoDbException e) {
} catch (DataIntegrityViolationException e) {

if (e.getCause() instanceof MongoException mongoException) {

Expand Down
Loading

0 comments on commit dced760

Please sign in to comment.