diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java new file mode 100644 index 000000000..c2260f553 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java @@ -0,0 +1,47 @@ +/* + * 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 + * + * 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 org.springframework.data.mongodb; + +import org.springframework.lang.Nullable; + +/** + * A specific {@link ClientSessionException} related to issues with a transaction such as aborted or non existing + * transactions. + * + * @author Christoph Strobl + * @since 2.1 + */ +public class MongoTransactionException extends ClientSessionException { + + /** + * Constructor for {@link MongoTransactionException}. + * + * @param msg the detail message. Must not be {@literal null}. + */ + public MongoTransactionException(String msg) { + super(msg); + } + + /** + * Constructor for {@link ClientSessionException}. + * + * @param msg the detail message. Can be {@literal null}. + * @param cause the root cause. Can be {@literal null}. + */ + public MongoTransactionException(@Nullable String msg, @Nullable Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java index 4e68d52a2..302da62b0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java @@ -30,6 +30,7 @@ import org.springframework.dao.PermissionDeniedDataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.BulkOperationException; import org.springframework.data.mongodb.ClientSessionException; +import org.springframework.data.mongodb.MongoTransactionException; import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.util.MongoDbErrorCodes; import org.springframework.lang.Nullable; @@ -128,6 +129,10 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); } else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) { 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); } return new UncategorizedMongoDbException(ex.getMessage(), ex); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoDbErrorCodes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoDbErrorCodes.java index 73ccc381b..bce052d3b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoDbErrorCodes.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoDbErrorCodes.java @@ -33,12 +33,14 @@ public final class MongoDbErrorCodes { static HashMap duplicateKeyCodes; static HashMap invalidDataAccessApiUsageExeption; static HashMap permissionDeniedCodes; + static HashMap clientSessionCodes; + static HashMap transactionCodes; static HashMap errorCodes; static { - dataAccessResourceFailureCodes = new HashMap(10); + dataAccessResourceFailureCodes = new HashMap<>(10); dataAccessResourceFailureCodes.put(6, "HostUnreachable"); dataAccessResourceFailureCodes.put(7, "HostNotFound"); dataAccessResourceFailureCodes.put(89, "NetworkTimeout"); @@ -52,7 +54,7 @@ public final class MongoDbErrorCodes { dataAccessResourceFailureCodes.put(13441, "BadOffsetInFile"); dataAccessResourceFailureCodes.put(13640, "DataFileHeaderCorrupt"); - dataIntegrityViolationCodes = new HashMap(6); + dataIntegrityViolationCodes = new HashMap<>(6); dataIntegrityViolationCodes.put(67, "CannotCreateIndex"); dataIntegrityViolationCodes.put(68, "IndexAlreadyExists"); dataIntegrityViolationCodes.put(85, "IndexOptionsConflict"); @@ -60,13 +62,13 @@ public final class MongoDbErrorCodes { dataIntegrityViolationCodes.put(112, "WriteConflict"); dataIntegrityViolationCodes.put(117, "ConflictingOperationInProgress"); - duplicateKeyCodes = new HashMap(3); + duplicateKeyCodes = new HashMap<>(3); duplicateKeyCodes.put(3, "OBSOLETE_DuplicateKey"); duplicateKeyCodes.put(84, "DuplicateKeyValue"); duplicateKeyCodes.put(11000, "DuplicateKey"); duplicateKeyCodes.put(11001, "DuplicateKey"); - invalidDataAccessApiUsageExeption = new HashMap(); + invalidDataAccessApiUsageExeption = new HashMap<>(); invalidDataAccessApiUsageExeption.put(5, "GraphContainsCycle"); invalidDataAccessApiUsageExeption.put(9, "FailedToParse"); invalidDataAccessApiUsageExeption.put(14, "TypeMismatch"); @@ -99,7 +101,7 @@ public final class MongoDbErrorCodes { invalidDataAccessApiUsageExeption.put(17280, "KeyTooLong"); invalidDataAccessApiUsageExeption.put(13334, "ShardKeyTooBig"); - permissionDeniedCodes = new HashMap(); + permissionDeniedCodes = new HashMap<>(); permissionDeniedCodes.put(11, "UserNotFound"); permissionDeniedCodes.put(18, "AuthenticationFailed"); permissionDeniedCodes.put(31, "RoleNotFound"); @@ -109,12 +111,29 @@ public final class MongoDbErrorCodes { permissionDeniedCodes.put(16704, "CannotAuthenticateToAdminDB"); permissionDeniedCodes.put(16705, "CannotAuthenticateToAdminDB"); - errorCodes = new HashMap(); + clientSessionCodes = new HashMap<>(); + clientSessionCodes.put(206, "NoSuchSession"); + clientSessionCodes.put(213, "DuplicateSession"); + clientSessionCodes.put(228, "SessionTransferIncomplete"); + clientSessionCodes.put(264, "TooManyLogicalSessions"); + + transactionCodes = new HashMap<>(); + transactionCodes.put(217, "IncompleteTransactionHistory"); + transactionCodes.put(225, "TransactionTooOld"); + transactionCodes.put(244, "TransactionAborted"); + transactionCodes.put(251, "NoSuchTransaction"); + transactionCodes.put(256, "TransactionCommitted"); + transactionCodes.put(257, "TransactionToLarge"); + transactionCodes.put(263, "OperationNotSupportedInTransaction"); + transactionCodes.put(267, "PreparedTransactionInProgress"); + + errorCodes = new HashMap<>(); errorCodes.putAll(dataAccessResourceFailureCodes); errorCodes.putAll(dataIntegrityViolationCodes); errorCodes.putAll(duplicateKeyCodes); errorCodes.putAll(invalidDataAccessApiUsageExeption); errorCodes.putAll(permissionDeniedCodes); + errorCodes.putAll(clientSessionCodes); } public static boolean isDataIntegrityViolationCode(@Nullable Integer errorCode) { @@ -140,4 +159,26 @@ public final class MongoDbErrorCodes { public static String getErrorDescription(@Nullable Integer errorCode) { return errorCode == null ? null : errorCodes.get(errorCode); } + + /** + * Check if the given error code matches a know session related error. + * + * @param errorCode the error code to check. + * @return {@literal true} if error matches. + * @since 2.1 + */ + public static boolean isClientSessionFailureCode(@Nullable Integer errorCode) { + return errorCode == null ? null : clientSessionCodes.containsKey(errorCode); + } + + /** + * Check if the given error code matches a know transaction related error. + * + * @param errorCode the error code to check. + * @return {@literal true} if error matches. + * @since 2.1 + */ + public static boolean isTransactionFailureCode(@Nullable Integer errorCode) { + return errorCode == null ? null : transactionCodes.containsKey(errorCode); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java index 272c5eac4..b86e793a4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java @@ -17,25 +17,20 @@ package org.springframework.data.mongodb.core; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import java.io.IOException; import java.net.UnknownHostException; -import com.mongodb.WriteConcern; import org.bson.BsonDocument; -import org.bson.Document; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; import org.springframework.core.NestedRuntimeException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.data.mongodb.ClientSessionException; +import org.springframework.data.mongodb.MongoTransactionException; import org.springframework.data.mongodb.UncategorizedMongoDbException; import com.mongodb.MongoCursorNotFoundException; @@ -135,6 +130,28 @@ public class MongoExceptionTranslatorUnitTests { assertThat(translator.translateExceptionIfPossible(exception), is(nullValue())); } + @Test // DATAMONGO-2045 + public void translateSessionExceptions() { + + checkTranslatedMongoException(ClientSessionException.class, 206); + checkTranslatedMongoException(ClientSessionException.class, 213); + checkTranslatedMongoException(ClientSessionException.class, 228); + checkTranslatedMongoException(ClientSessionException.class, 264); + } + + @Test // DATAMONGO-2045 + public void translateTransactionExceptions() { + + checkTranslatedMongoException(MongoTransactionException.class, 217); + checkTranslatedMongoException(MongoTransactionException.class, 225); + checkTranslatedMongoException(MongoTransactionException.class, 244); + checkTranslatedMongoException(MongoTransactionException.class, 251); + checkTranslatedMongoException(MongoTransactionException.class, 256); + checkTranslatedMongoException(MongoTransactionException.class, 257); + checkTranslatedMongoException(MongoTransactionException.class, 263); + checkTranslatedMongoException(MongoTransactionException.class, 267); + } + private void checkTranslatedMongoException(Class clazz, int code) { DataAccessException translated = translator.translateExceptionIfPossible(new MongoException(code, ""));