diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java index fc480bf3e..a3cdf34af 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java @@ -36,11 +36,12 @@ public @interface CompoundIndex { /** * The actual index definition in JSON format. The keys of the JSON document are the fields to be indexed, the values - * define the index direction (1 for ascending, -1 for descending). + * define the index direction (1 for ascending, -1 for descending).
+ * If left empty on nested document, the whole document will be indexed. * * @return */ - String def(); + String def() default ""; /** * It does not actually make sense to use that attribute as the direction has to be defined in the {@link #def()} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java index 1e51bad66..e50f14761 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mongodb.core.index.Index.Duplicates; @@ -31,6 +32,8 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; import com.mongodb.util.JSON; @@ -158,7 +161,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { private List potentiallyCreateCompoundIndexDefinitions(String dotPath, String collection, Class type) { - if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null) { + if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null + && AnnotationUtils.findAnnotation(type, CompoundIndex.class) == null) { return Collections.emptyList(); } @@ -176,38 +180,77 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { protected List createCompoundIndexDefinitions(String dotPath, String fallbackCollection, Class type) { + List indexDefinitions = new ArrayList(); CompoundIndexes indexes = AnnotationUtils.findAnnotation(type, CompoundIndexes.class); - List indexDefinitions = new ArrayList( - indexes.value().length); - for (CompoundIndex index : indexes.value()) { + if (indexes != null) { + for (CompoundIndex index : indexes.value()) { + indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index)); + } + } - String path = StringUtils.hasText(index.name()) ? index.name() : dotPath; - String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection; + CompoundIndex index = AnnotationUtils.findAnnotation(type, CompoundIndex.class); - CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition((DBObject) JSON.parse(index.def())); - if (!index.useGeneratedName()) { - indexDefinition.named(index.name()); - } - if (index.unique()) { - indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN); - } - if (index.sparse()) { - indexDefinition.sparse(); - } - if (index.background()) { - indexDefinition.background(); - } - if (index.expireAfterSeconds() >= 0) { - indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS); - } - - indexDefinitions.add(new IndexDefinitionHolder(path, indexDefinition, collection)); + if (index != null) { + indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index)); } return indexDefinitions; } + protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection, + CompoundIndex index) { + + CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(resolveCompoundIndexKeyFromStringDefinition( + dotPath, index.def())); + + if (!index.useGeneratedName()) { + indexDefinition.named(index.name()); + } + + if (index.unique()) { + indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN); + } + + if (index.sparse()) { + indexDefinition.sparse(); + } + + if (index.background()) { + indexDefinition.background(); + } + + if (index.expireAfterSeconds() >= 0) { + indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS); + } + + String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection; + return new IndexDefinitionHolder(dotPath, indexDefinition, collection); + } + + private DBObject resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString) { + + if (!StringUtils.hasText(dotPath) && !StringUtils.hasText(keyDefinitionString)) { + throw new InvalidDataAccessApiUsageException("Cannot create index on root level for empty keys."); + } + + if (!StringUtils.hasText(keyDefinitionString)) { + return new BasicDBObject(dotPath, 1); + } + + DBObject dbo = (DBObject) JSON.parse(keyDefinitionString); + if (!StringUtils.hasText(dotPath)) { + return dbo; + } + + BasicDBObjectBuilder dboBuilder = new BasicDBObjectBuilder(); + + for (String key : dbo.keySet()) { + dboBuilder.add(dotPath + "." + key, dbo.get(key)); + } + return dboBuilder.get(); + } + /** * Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link Indexed} for given * {@link MongoPersistentProperty}. @@ -223,24 +266,25 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { Indexed index = persitentProperty.findAnnotation(Indexed.class); String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection; - Index indexDefinition = new Index(); + Index indexDefinition = new Index().on(dotPath, + IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC); if (!index.useGeneratedName()) { - indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : persitentProperty.getFieldName()); + indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : dotPath); } - indexDefinition.on(persitentProperty.getFieldName(), - IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC); - if (index.unique()) { indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN); } + if (index.sparse()) { indexDefinition.sparse(); } + if (index.background()) { indexDefinition.background(); } + if (index.expireAfterSeconds() >= 0) { indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS); } @@ -306,7 +350,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { } /** - * Get the {@liteal "dot"} path used to create the index. + * Get the {@literal "dot"} path used to create the index. * * @return */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java index 6fa033b7a..b8dcd9bd7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java @@ -277,7 +277,7 @@ public class MongoPersistentEntityIndexResolverUnitTests { List indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class); assertThat(indexDefinitions, hasSize(1)); - assertIndexPathAndCollection("compound_index", "CompoundIndexOnLevelZero", indexDefinitions.get(0)); + assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); } /** @@ -324,7 +324,56 @@ public class MongoPersistentEntityIndexResolverUnitTests { equalTo(new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).add("sparse", true) .add("background", true).add("expireAfterSeconds", 10L).get())); assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get())); + } + /** + * @see DATAMONGO-929 + */ + @Test + public void compoundIndexPathOnLevelOneIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelOne.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection(new String[] { "zero.foo", "zero.bar" }, "CompoundIndexOnLevelOne", + indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-929 + */ + @Test + public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelOneWithEmptyIndexDefinition.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection(new String[] { "zero" }, "CompoundIndexOnLevelZeroWithEmptyIndexDef", + indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-929 + */ + @Test + public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(SingleCompoundIndex.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); + } + + @Document(collection = "CompoundIndexOnLevelOne") + static class CompoundIndexOnLevelOne { + + CompoundIndexOnLevelZero zero; + } + + @Document(collection = "CompoundIndexOnLevelZeroWithEmptyIndexDef") + static class CompoundIndexOnLevelOneWithEmptyIndexDefinition { + + CompoundIndexOnLevelZeroWithEmptyIndexDef zero; } @Document(collection = "CompoundIndexOnLevelZero") @@ -332,6 +381,15 @@ public class MongoPersistentEntityIndexResolverUnitTests { dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true) }) static class CompoundIndexOnLevelZero {} + @CompoundIndexes({ @CompoundIndex(name = "compound_index", background = true, dropDups = true, + expireAfterSeconds = 10, sparse = true, unique = true) }) + static class CompoundIndexOnLevelZeroWithEmptyIndexDef {} + + @Document(collection = "CompoundIndexOnLevelZero") + @CompoundIndex(name = "compound_index", def = "{'foo': 1, 'bar': -1}", background = true, dropDups = true, + expireAfterSeconds = 10, sparse = true, unique = true) + static class SingleCompoundIndex {} + static class IndexDefinedOnSuperClass extends CompoundIndexOnLevelZero { } @@ -426,7 +484,15 @@ public class MongoPersistentEntityIndexResolverUnitTests { private static void assertIndexPathAndCollection(String expectedPath, String expectedCollection, IndexDefinitionHolder holder) { - assertThat(holder.getPath(), equalTo(expectedPath)); + assertIndexPathAndCollection(new String[] { expectedPath }, expectedCollection, holder); + } + + private static void assertIndexPathAndCollection(String[] expectedPaths, String expectedCollection, + IndexDefinitionHolder holder) { + + for (String expectedPath : expectedPaths) { + assertThat(holder.getIndexDefinition().getIndexKeys().containsField(expectedPath), equalTo(true)); + } assertThat(holder.getCollection(), equalTo(expectedCollection)); } }