diff --git a/pom.xml b/pom.xml index 114420f66b..dbe3e93daf 100644 --- a/pom.xml +++ b/pom.xml @@ -1,16 +1,16 @@ - + 4.0.0 org.springframework.data spring-data-mongodb-parent - 2.1.0.BUILD-SNAPSHOT + 2.1.0.DATAMONGO-2073-SNAPSHOT pom Spring Data MongoDB MongoDB support for Spring Data - http://projects.spring.io/spring-data-mongodb + https://projects.spring.io/spring-data-mongodb org.springframework.data.build @@ -39,7 +39,7 @@ Oliver Gierke ogierke at gopivotal.com Pivotal - http://www.gopivotal.com + https://pivotal.io Project Lead @@ -50,7 +50,7 @@ Thomas Risberg trisberg at vmware.com Pivotal - http://www.gopivotal.com + https://pivotal.io Developer @@ -61,7 +61,7 @@ Mark Pollack mpollack at gopivotal.com Pivotal - http://www.gopivotal.com + https://pivotal.io Developer @@ -72,7 +72,7 @@ Jon Brisbin jbrisbin at gopivotal.com Pivotal - http://www.gopivotal.com + https://pivotal.io Developer @@ -83,7 +83,7 @@ Thomas Darimont tdarimont at gopivotal.com Pivotal - http://www.gopivotal.com + https://pivotal.io Developer @@ -94,7 +94,7 @@ Christoph Strobl cstrobl at gopivotal.com Pivotal - http://www.gopivotal.com + https://pivotal.io Developer @@ -105,7 +105,7 @@ Mark Paluch mpaluch at pivotal.io Pivotal - http://www.pivotal.io + https://www.pivotal.io Developer diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index 9baccaa905..2dd5b61ffd 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -1,13 +1,13 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 org.springframework.data spring-data-mongodb-parent - 2.1.0.BUILD-SNAPSHOT + 2.1.0.DATAMONGO-2073-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index 47a5b7aba7..210e270258 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -1,12 +1,12 @@ - + 4.0.0 org.springframework.data spring-data-mongodb-parent - 2.1.0.BUILD-SNAPSHOT + 2.1.0.DATAMONGO-2073-SNAPSHOT ../pom.xml @@ -50,7 +50,7 @@ org.springframework.data spring-data-mongodb - 2.1.0.BUILD-SNAPSHOT + 2.1.0.DATAMONGO-2073-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index e5c865ea08..e3354d864a 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 2.1.0.BUILD-SNAPSHOT + 2.1.0.DATAMONGO-2073-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index b86dc2808c..14b8477ee5 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 2.1.0.BUILD-SNAPSHOT + 2.1.0.DATAMONGO-2073-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientClientSessionException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientClientSessionException.java new file mode 100644 index 0000000000..652f92283e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientClientSessionException.java @@ -0,0 +1,48 @@ +/* + * 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.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 2.1 + */ +public class TransientClientSessionException extends TransientMongoDbException { + + /** + * Constructor for {@link TransientClientSessionException}. + * + * @param msg the detail message. Must not be {@literal null}. + */ + public TransientClientSessionException(String msg) { + super(msg); + } + + /** + * 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); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbException.java new file mode 100644 index 0000000000..25bd3b501b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbException.java @@ -0,0 +1,49 @@ +/* + * 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.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 2.1 + */ +public class TransientMongoDbException extends TransientDataAccessException { + + /** + * Constructor for {@link TransientMongoDbException}. + * + * @param msg the detail message. Must not be {@literal null}. + */ + public TransientMongoDbException(String msg) { + super(msg); + } + + /** + * 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); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbTransactionException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbTransactionException.java new file mode 100644 index 0000000000..bc26cca868 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/TransientMongoDbTransactionException.java @@ -0,0 +1,46 @@ +/* + * 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 TransientClientSessionException} related to issues with a transaction such as fails on commit. + * + * @author Christoph Strobl + * @since 2.1 + */ +public class TransientMongoDbTransactionException extends TransientClientSessionException { + + /** + * Constructor for {@link TransientMongoDbTransactionException}. + * + * @param msg the detail message. Must not be {@literal null}. + */ + public TransientMongoDbTransactionException(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 TransientMongoDbTransactionException(@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 e3f985dab0..84021c737a 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 @@ -28,10 +28,14 @@ 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.BulkOperationException; 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.TransientMongoDbTransactionException; import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.util.MongoDbErrorCodes; import org.springframework.lang.Nullable; @@ -74,6 +78,21 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator @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) { @@ -119,7 +138,9 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) { // All other MongoExceptions if (ex instanceof MongoException) { - int code = ((MongoException) ex).getCode(); + MongoException mongoException = (MongoException) ex; + int code = mongoException.getCode(); + boolean isTransient = isTransientFailure(mongoException); if (MongoDbErrorCodes.isDuplicateKeyCode(code)) { return new DuplicateKeyException(ex.getMessage(), ex); @@ -131,10 +152,13 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) { } else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) { return new PermissionDeniedDataAccessException(ex.getMessage(), ex); } else if (MongoDbErrorCodes.isClientSessionFailureCode(code)) { - return new ClientSessionException(ex.getMessage(), ex); + return isTransient ? new TransientClientSessionException(ex.getMessage(), ex) + : new ClientSessionException(ex.getMessage(), ex); } else if (MongoDbErrorCodes.isTransactionFailureCode(code)) { - return new MongoTransactionException(ex.getMessage(), ex); + return isTransient ? new TransientMongoDbTransactionException(ex.getMessage(), ex) + : new MongoTransactionException(ex.getMessage(), ex); } + return new UncategorizedMongoDbException(ex.getMessage(), ex); } @@ -153,4 +177,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 2.1 + */ + 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); + } } 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 b86e793a49..039089ae31 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 @@ -18,19 +18,27 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import java.beans.Transient; import java.net.UnknownHostException; +import com.mongodb.MongoCommandException; +import com.mongodb.MongoWriteException; +import com.mongodb.WriteError; import org.bson.BsonDocument; import org.junit.Before; import org.junit.Test; import org.springframework.core.NestedRuntimeException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.dao.TransientDataAccessException; import org.springframework.data.mongodb.ClientSessionException; import org.springframework.data.mongodb.MongoTransactionException; +import org.springframework.data.mongodb.TransientMongoDbException; +import org.springframework.data.mongodb.TransientMongoDbTransactionException; import org.springframework.data.mongodb.UncategorizedMongoDbException; import com.mongodb.MongoCursorNotFoundException; @@ -152,6 +160,38 @@ public void translateTransactionExceptions() { checkTranslatedMongoException(MongoTransactionException.class, 267); } + @Test // DATAMONGO-2073 + public void translateTransientTransactionExceptions() { + + MongoException source = new MongoException(267, "PreparedTransactionInProgress"); + source.addLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL); + + expectExceptionWithCauseMessage(translator.translateExceptionIfPossible(source), + TransientMongoDbTransactionException.class, "PreparedTransactionInProgress"); + } + + @Test // DATAMONGO-2073 + public void translateMongoExceptionWithTransientLabelToTransientMongoDbException() { + + MongoException exception = new MongoException(0, ""); + exception.addLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL); + DataAccessException translatedException = translator.translateExceptionIfPossible(exception); + + expectExceptionWithCauseMessage(translatedException, TransientMongoDbException.class); + } + + @Test // DATAMONGO-2073 + public void wrapsTranslatedExceptionsWhenTransientLabelPresent() { + + MongoException exception = new MongoWriteException(new WriteError(112, "WriteConflict", new BsonDocument()), null); + exception.addLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL); + + DataAccessException translatedException = translator.translateExceptionIfPossible(exception); + + assertThat(translatedException, is(instanceOf(TransientMongoDbException.class))); + assertThat(translatedException.getCause(), is(instanceOf(DataIntegrityViolationException.class))); + } + private void checkTranslatedMongoException(Class clazz, int code) { DataAccessException translated = translator.translateExceptionIfPossible(new MongoException(code, ""));