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 9be051e30..737655e11 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 @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -25,6 +26,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.dao.PermissionDeniedDataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.util.ClassUtils; @@ -86,12 +88,15 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator int code = ((MongoException) ex).getCode(); - if (code == 11000 || code == 11001) { + if (MongoDbErrorCodes.isDuplicateKeyCode(code)) { throw new DuplicateKeyException(ex.getMessage(), ex); - } else if (code == 12000 || code == 13440) { + } else if (MongoDbErrorCodes.isDataAccessResourceFailureCode(code)) { throw new DataAccessResourceFailureException(ex.getMessage(), ex); - } else if (code == 10003 || code == 12001 || code == 12010 || code == 12011 || code == 12012) { + } else if (MongoDbErrorCodes.isInvalidDataAccessApiUsageCode(code) || code == 10003 || code == 12001 + || code == 12010 || code == 12011 || code == 12012) { throw new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) { + throw new PermissionDeniedDataAccessException(ex.getMessage(), ex); } return new UncategorizedMongoDbException(ex.getMessage(), ex); } @@ -101,4 +106,126 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator // that translation should not occur. return null; } + + /** + * {@link MongoDbErrorCodes} holds MongoDB specific error codes outlined in {@literal mongo/base/error_codes.err}. + * + * @author Christoph Strobl + * @since 1.8 + */ + public static final class MongoDbErrorCodes { + + static HashMap dataAccessResourceFailureCodes; + static HashMap dataIntegrityViolationCodes; + static HashMap duplicateKeyCodes; + static HashMap invalidDataAccessApiUsageExeption; + static HashMap permissionDeniedCodes; + + static HashMap errorCodes; + + static { + + dataAccessResourceFailureCodes = new HashMap(10); + dataAccessResourceFailureCodes.put(6, "HostUnreachable"); + dataAccessResourceFailureCodes.put(7, "HostNotFound"); + dataAccessResourceFailureCodes.put(89, "NetworkTimeout"); + dataAccessResourceFailureCodes.put(91, "ShutdownInProgress"); + dataAccessResourceFailureCodes.put(12000, "SlaveDelayDifferential"); + dataAccessResourceFailureCodes.put(10084, "CannotFindMapFile64Bit"); + dataAccessResourceFailureCodes.put(10085, "CannotFindMapFile"); + dataAccessResourceFailureCodes.put(10357, "ShutdownInProgress"); + dataAccessResourceFailureCodes.put(10359, "Header==0"); + dataAccessResourceFailureCodes.put(13440, "BadOffsetInFile"); + dataAccessResourceFailureCodes.put(13441, "BadOffsetInFile"); + dataAccessResourceFailureCodes.put(13640, "DataFileHeaderCorrupt"); + + dataIntegrityViolationCodes = new HashMap(6); + dataIntegrityViolationCodes.put(67, "CannotCreateIndex"); + dataIntegrityViolationCodes.put(68, "IndexAlreadyExists"); + dataIntegrityViolationCodes.put(85, "IndexOptionsConflict"); + dataIntegrityViolationCodes.put(86, "IndexKeySpecsConflict"); + dataIntegrityViolationCodes.put(112, "WriteConflict"); + dataIntegrityViolationCodes.put(117, "ConflictingOperationInProgress"); + + 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.put(5, "GraphContainsCycle"); + invalidDataAccessApiUsageExeption.put(9, "FailedToParse"); + invalidDataAccessApiUsageExeption.put(14, "TypeMismatch"); + invalidDataAccessApiUsageExeption.put(15, "Overflow"); + invalidDataAccessApiUsageExeption.put(16, "InvalidLength"); + invalidDataAccessApiUsageExeption.put(20, "IllegalOperation"); + invalidDataAccessApiUsageExeption.put(21, "EmptyArrayOperation"); + invalidDataAccessApiUsageExeption.put(22, "InvalidBSON"); + invalidDataAccessApiUsageExeption.put(23, "AlreadyInitialized"); + invalidDataAccessApiUsageExeption.put(29, "NonExistentPath"); + invalidDataAccessApiUsageExeption.put(30, "InvalidPath"); + invalidDataAccessApiUsageExeption.put(40, "ConflictingUpdateOperators"); + invalidDataAccessApiUsageExeption.put(45, "UserDataInconsistent"); + invalidDataAccessApiUsageExeption.put(30, "DollarPrefixedFieldName"); + invalidDataAccessApiUsageExeption.put(52, "InvalidPath"); + invalidDataAccessApiUsageExeption.put(53, "InvalidIdField"); + invalidDataAccessApiUsageExeption.put(54, "NotSingleValueField"); + invalidDataAccessApiUsageExeption.put(55, "InvalidDBRef"); + invalidDataAccessApiUsageExeption.put(56, "EmptyFieldName"); + invalidDataAccessApiUsageExeption.put(57, "DottedFieldName"); + invalidDataAccessApiUsageExeption.put(59, "CommandNotFound"); + invalidDataAccessApiUsageExeption.put(60, "DatabaseNotFound"); + invalidDataAccessApiUsageExeption.put(61, "ShardKeyNotFound"); + invalidDataAccessApiUsageExeption.put(62, "OplogOperationUnsupported"); + invalidDataAccessApiUsageExeption.put(66, "ImmutableField"); + invalidDataAccessApiUsageExeption.put(72, "InvalidOptions"); + invalidDataAccessApiUsageExeption.put(115, "CommandNotSupported"); + invalidDataAccessApiUsageExeption.put(116, "DocTooLargeForCapped"); + invalidDataAccessApiUsageExeption.put(130, "SymbolNotFound"); + invalidDataAccessApiUsageExeption.put(17280, "KeyTooLong"); + invalidDataAccessApiUsageExeption.put(13334, "ShardKeyTooBig"); + + permissionDeniedCodes = new HashMap(); + permissionDeniedCodes.put(11, "UserNotFound"); + permissionDeniedCodes.put(18, "AuthenticationFailed"); + permissionDeniedCodes.put(31, "RoleNotFound"); + permissionDeniedCodes.put(32, "RolesNotRelated"); + permissionDeniedCodes.put(33, "PrvilegeNotFound"); + permissionDeniedCodes.put(15847, "CannotAuthenticate"); + permissionDeniedCodes.put(16704, "CannotAuthenticateToAdminDB"); + permissionDeniedCodes.put(16705, "CannotAuthenticateToAdminDB"); + + errorCodes = new HashMap(); + errorCodes.putAll(dataAccessResourceFailureCodes); + errorCodes.putAll(dataIntegrityViolationCodes); + errorCodes.putAll(duplicateKeyCodes); + errorCodes.putAll(invalidDataAccessApiUsageExeption); + errorCodes.putAll(permissionDeniedCodes); + } + + public static boolean isDataIntegrityViolationCode(Integer errorCode) { + return errorCode == null ? false : dataIntegrityViolationCodes.containsKey(errorCode); + } + + public static boolean isDataAccessResourceFailureCode(Integer errorCode) { + return errorCode == null ? false : dataAccessResourceFailureCodes.containsKey(errorCode); + } + + public static boolean isDuplicateKeyCode(Integer errorCode) { + return errorCode == null ? false : duplicateKeyCodes.containsKey(errorCode); + } + + public static boolean isPermissionDeniedCode(Integer errorCode) { + return errorCode == null ? false : permissionDeniedCodes.containsKey(errorCode); + } + + public static boolean isInvalidDataAccessApiUsageCode(Integer errorCode) { + return errorCode == null ? false : invalidDataAccessApiUsageExeption.containsKey(errorCode); + } + + public static String getErrorDescription(Integer errorCode) { + return errorCode == null ? null : errorCodes.get(errorCode); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java index 1b0fbfb61..11cc7db9b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java @@ -21,15 +21,21 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationListener; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContextEvent; import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoExceptionTranslator.MongoDbErrorCodes; 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; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +import com.mongodb.DBObject; +import com.mongodb.MongoException; /** * Component that inspects {@link MongoPersistentEntity} instances contained in the given {@link MongoMappingContext} @@ -129,9 +135,34 @@ public class MongoPersistentEntityIndexCreator implements ApplicationListener context) { return this.mappingContext.equals(context); } + + private DBObject fetchIndexInformation(IndexDefinitionHolder indexDefinition) { + + if (indexDefinition == null) { + return null; + } + + try { + + Object indexNameToLookUp = indexDefinition.getIndexOptions().get("name"); + + for (DBObject index : mongoDbFactory.getDb().getCollection(indexDefinition.getCollection()).getIndexInfo()) { + if (ObjectUtils.nullSafeEquals(indexNameToLookUp, index.get("name"))) { + return index; + } + } + + } catch (Exception e) { + LOGGER.debug( + String.format("Failed to load index information for collection '%s'.", indexDefinition.getCollection()), e); + } + + return null; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java index 0d6fc90fe..529828fe4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java @@ -18,25 +18,37 @@ package org.springframework.data.mongodb.core.index; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.net.UnknownHostException; import java.util.Arrays; import java.util.List; import org.hamcrest.Matchers; +import org.hamcrest.core.IsInstanceOf; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +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; import org.springframework.data.mongodb.test.util.CleanMongoDB; import org.springframework.data.mongodb.test.util.MongoVersionRule; import org.springframework.data.util.Version; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.mongodb.MongoClient; +import com.mongodb.MongoCommandException; + /** * Integration tests for {@link MongoPersistentEntityIndexCreator}. * @@ -54,6 +66,8 @@ public class MongoPersistentEntityIndexCreatorIntegrationTests { public static @ClassRule RuleChain rules = RuleChain.outerRule(MongoVersionRule.atLeast(new Version(2, 6))).around( CleanMongoDB.indexes(Arrays.asList(SAMPLE_TYPE_COLLECTION_NAME, RECURSIVE_TYPE_COLLECTION_NAME))); + public @Rule ExpectedException expectedException = ExpectedException.none(); + @Autowired @Qualifier("mongo1") MongoOperations templateOne; @Autowired @Qualifier("mongo2") MongoOperations templateTwo; @@ -81,6 +95,28 @@ public class MongoPersistentEntityIndexCreatorIntegrationTests { assertThat(indexInfo, Matchers. hasItem(hasProperty("name", is("firstName")))); } + /** + * @DATAMONGO-1125 + */ + @Test + public void createIndexShouldThrowMeaningfulExceptionWhenIndexCreationFails() throws UnknownHostException { + + expectedException.expect(DataIntegrityViolationException.class); + expectedException.expectMessage("collection 'datamongo-1125'"); + expectedException.expectMessage("dalinar.kohlin"); + expectedException.expectMessage("lastname"); + expectedException.expectCause(IsInstanceOf. instanceOf(MongoCommandException.class)); + + MongoPersistentEntityIndexCreator indexCreator = new MongoPersistentEntityIndexCreator(new MongoMappingContext(), + new SimpleMongoDbFactory(new MongoClient(), "issue")); + + indexCreator.createIndex(new IndexDefinitionHolder("dalinar.kohlin", new Index().named("stormlight") + .on("lastname", Direction.ASC).unique(), "datamongo-1125")); + + indexCreator.createIndex(new IndexDefinitionHolder("dalinar.kohlin", new Index().named("stormlight") + .on("lastname", Direction.ASC).sparse(), "datamongo-1125")); + } + @Document(collection = RECURSIVE_TYPE_COLLECTION_NAME) static abstract class RecursiveGenericType> { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java index 815dd775d..093286aab 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -28,11 +28,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.context.ApplicationContext; +import org.springframework.dao.DataAccessException; import org.springframework.data.geo.Point; import org.springframework.data.mapping.context.MappingContextEvent; import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoExceptionTranslator; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -43,6 +46,7 @@ import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBObject; +import com.mongodb.MongoException; /** * Unit tests for {@link MongoPersistentEntityIndexCreator}. @@ -211,6 +215,36 @@ public class MongoPersistentEntityIndexCreatorUnitTests { assertThat(collectionNameCapturer.getValue(), equalTo("indexedDocumentWrapper")); } + /** + * @see DATAMONGO-1125 + */ + @Test(expected = DataAccessException.class) + public void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcerns() { + + when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); + doThrow(new MongoException(6, "HostUnreachable")).when(collection).createIndex(Mockito.any(DBObject.class), + Mockito.any(DBObject.class)); + + MongoMappingContext mappingContext = prepareMappingContext(Person.class); + + new MongoPersistentEntityIndexCreator(mappingContext, factory); + } + + /** + * @see DATAMONGO-1125 + */ + @Test(expected = ClassCastException.class) + public void createIndexShouldNotConvertUnknownExceptionTypes() { + + when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); + doThrow(new ClassCastException("o_O")).when(collection).createIndex(Mockito.any(DBObject.class), + Mockito.any(DBObject.class)); + + MongoMappingContext mappingContext = prepareMappingContext(Person.class); + + new MongoPersistentEntityIndexCreator(mappingContext, factory); + } + private static MongoMappingContext prepareMappingContext(Class type) { MongoMappingContext mappingContext = new MongoMappingContext();