diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndexDefinition.java new file mode 100644 index 000000000..890d3cf64 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndexDefinition.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 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.core.index; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * @author Christoph Strobl + * @since 1.5 + */ +public class CompoundIndexDefinition extends Index { + + private DBObject keys; + + public CompoundIndexDefinition(DBObject keys) { + this.keys = keys; + } + + @Override + public DBObject getIndexKeys() { + + BasicDBObject dbo = new BasicDBObject(); + dbo.putAll(this.keys); + dbo.putAll(super.getIndexKeys()); + return dbo; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java index 8ddc70885..3a14f6551 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2014 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. @@ -27,6 +27,7 @@ import com.mongodb.DBObject; * @author Jon Brisbin * @author Oliver Gierke * @author Laurent Canet + * @author Christoph Strobl */ public class GeospatialIndex implements IndexDefinition { @@ -38,6 +39,7 @@ public class GeospatialIndex implements IndexDefinition { private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D; private Double bucketSize = 1.0; private String additionalField; + private String collection; /** * Creates a new {@link GeospatialIndex} for the given field. @@ -120,6 +122,23 @@ public class GeospatialIndex implements IndexDefinition { return this; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexDefinition#getCollection() + */ + @Override + public String getCollection() { + return collection; + } + + /** + * @param collection + * @since 1.5 + */ + public void setCollection(String collection) { + this.collection = collection; + } + public DBObject getIndexKeys() { DBObject dbo = new BasicDBObject(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java index c7bbd7c8c..b12fed883 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2014 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. @@ -17,13 +17,19 @@ package org.springframework.data.mongodb.core.index; import java.util.LinkedHashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.query.Order; +import org.springframework.util.Assert; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +/** + * @author Oliver Gierke + * @author Christoph Strobl + */ @SuppressWarnings("deprecation") public class Index implements IndexDefinition { @@ -41,6 +47,12 @@ public class Index implements IndexDefinition { private boolean sparse = false; + private boolean background = false; + + private long expire = -1; + + private String collection; + public Index() {} public Index(String key, Direction direction) { @@ -104,6 +116,61 @@ public class Index implements IndexDefinition { return this; } + /** + * Build the index in background (non blocking). + * + * @return + * @since 1.5 + */ + public Index background() { + + this.background = true; + return this; + } + + /** + * Specifies TTL in seconds. + * + * @param value + * @return + * @since 1.5 + */ + public Index expire(long value) { + return expire(value, TimeUnit.SECONDS); + } + + /** + * Specifies TTL with given {@link TimeUnit}. + * + * @param value + * @param unit + * @return + * @since 1.5 + */ + public Index expire(long value, TimeUnit unit) { + + Assert.notNull(unit, "TimeUnit for expiration must not be null."); + this.expire = unit.toSeconds(value); + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexDefinition#getCollection() + */ + @Override + public String getCollection() { + return collection; + } + + /** + * @param collection + * @since 1.5 + */ + public void setCollection(String collection) { + this.collection = collection; + } + /** * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @param duplicates @@ -125,9 +192,11 @@ public class Index implements IndexDefinition { } public DBObject getIndexOptions() { + if (name == null && !unique) { return null; } + DBObject dbo = new BasicDBObject(); if (name != null) { dbo.put("name", name); @@ -141,6 +210,12 @@ public class Index implements IndexDefinition { if (sparse) { dbo.put("sparse", true); } + if (background) { + dbo.put("background", true); + } + if (expire >= 0) { + dbo.put("expireAfterSeconds", expire); + } return dbo; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexDefinition.java index e6317e9ae..ea28c3b68 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexDefinition.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright (c) 2011-2014 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import com.mongodb.DBObject; /** * @author Jon Brisbin + * @author Christoph Strobl */ public interface IndexDefinition { @@ -27,4 +28,11 @@ public interface IndexDefinition { DBObject getIndexOptions(); + /** + * Get the collection name for the index. + * + * @return + * @since 1.5 + */ + String getCollection(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexResolver.java new file mode 100644 index 000000000..2f829fe54 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexResolver.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014 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.core.index; + +/** + * {@link IndexResolver} finds those {@link IndexDefinition}s to be created for a given class. + * + * @author Christoph Strobl + * @since 1.5 + */ +public interface IndexResolver { + + /** + * Find and create {@link IndexDefinition}s for properties of given {@code type}. {@link IndexDefinition}s are created + * for properties and types with {@link Indexed}, {@link CompoundIndexes} or {@link GeoSpatialIndexed}. + * + * @param type + * @return Empty {@link Iterable} in case no {@link IndexDefinition} could be resolved for type. + */ + Iterable resolveIndexForClass(Class type); + +} 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 02ad1c50c..d0760eb0c 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 @@ -22,19 +22,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationListener; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.PropertyHandler; 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.mapping.Document; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; 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.DBObject; -import com.mongodb.util.JSON; /** * Component that inspects {@link MongoPersistentEntity} instances contained in the given {@link MongoMappingContext} @@ -45,6 +40,7 @@ import com.mongodb.util.JSON; * @author Philipp Schneider * @author Johno Crawford * @author Laurent Canet + * @author Christoph Strobl */ public class MongoPersistentEntityIndexCreator implements ApplicationListener, MongoPersistentProperty>> { @@ -54,21 +50,37 @@ public class MongoPersistentEntityIndexCreator implements private final Map, Boolean> classesSeen = new ConcurrentHashMap, Boolean>(); private final MongoDbFactory mongoDbFactory; private final MongoMappingContext mappingContext; + private final IndexResolver indexResolver; /** * Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and * {@link MongoDbFactory}. * - * @param mappingContext must not be {@literal null} - * @param mongoDbFactory must not be {@literal null} + * @param mappingContext must not be {@literal null}. + * @param mongoDbFactory must not be {@literal null}. */ public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) { + this(mappingContext, mongoDbFactory, new MongoPersistentEntityIndexResolver(mappingContext)); + } + + /** + * Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and + * {@link MongoDbFactory}. + * + * @param mappingContext must not be {@literal null}. + * @param mongoDbFactory must not be {@literal null}. + * @param indexResolver must not be {@literal null}. + */ + public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory, + IndexResolver indexResolver) { Assert.notNull(mongoDbFactory); Assert.notNull(mappingContext); + Assert.notNull(indexResolver); this.mongoDbFactory = mongoDbFactory; this.mappingContext = mappingContext; + this.indexResolver = indexResolver; for (MongoPersistentEntity entity : mappingContext.getPersistentEntities()) { checkForIndexes(entity); @@ -93,87 +105,34 @@ public class MongoPersistentEntityIndexCreator implements } } - protected void checkForIndexes(final MongoPersistentEntity entity) { + private void checkForIndexes(final MongoPersistentEntity entity) { final Class type = entity.getType(); if (!classesSeen.containsKey(type)) { + + this.classesSeen.put(type, Boolean.TRUE); + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Analyzing class " + type + " for index information."); } - // Make sure indexes get created - if (type.isAnnotationPresent(CompoundIndexes.class)) { - CompoundIndexes indexes = type.getAnnotation(CompoundIndexes.class); - for (CompoundIndex index : indexes.value()) { - - String indexColl = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection(); - DBObject definition = (DBObject) JSON.parse(index.def()); - - ensureIndex(indexColl, index.name(), definition, index.unique(), index.dropDups(), index.sparse(), - index.background(), index.expireAfterSeconds()); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Created compound index " + index); - } - } - } - - entity.doWithProperties(new PropertyHandler() { - public void doWithPersistentProperty(MongoPersistentProperty property) { - - if (property.isAnnotationPresent(Indexed.class)) { - - Indexed index = property.findAnnotation(Indexed.class); - String name = index.name(); - - if (!StringUtils.hasText(name)) { - name = property.getFieldName(); - } else { - if (!name.equals(property.getName()) && index.unique() && !index.sparse()) { - // Names don't match, and sparse is not true. This situation will generate an error on the server. - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("The index name " + name + " doesn't match this property name: " + property.getName() - + ". Setting sparse=true on this index will prevent errors when inserting documents."); - } - } - } - - String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection(); - int direction = index.direction() == IndexDirection.ASCENDING ? 1 : -1; - DBObject definition = new BasicDBObject(property.getFieldName(), direction); - - ensureIndex(collection, name, definition, index.unique(), index.dropDups(), index.sparse(), - index.background(), index.expireAfterSeconds()); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Created property index " + index); - } - - } else if (property.isAnnotationPresent(GeoSpatialIndexed.class)) { - - GeoSpatialIndexed index = property.findAnnotation(GeoSpatialIndexed.class); - - GeospatialIndex indexObject = new GeospatialIndex(property.getFieldName()); - indexObject.withMin(index.min()).withMax(index.max()); - indexObject.named(StringUtils.hasText(index.name()) ? index.name() : property.getName()); - indexObject.typed(index.type()).withBucketSize(index.bucketSize()) - .withAdditionalField(index.additionalField()); - - String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection(); - mongoDbFactory.getDb().getCollection(collection) - .ensureIndex(indexObject.getIndexKeys(), indexObject.getIndexOptions()); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("Created %s for entity %s in collection %s! ", indexObject, entity.getType(), - collection)); - } - } - } - }); - - classesSeen.put(type, true); + checkForAndCreateIndexes(entity); } } + protected void checkForAndCreateIndexes(MongoPersistentEntity entity) { + + if (entity.findAnnotation(Document.class) != null) { + for (IndexDefinition indexToCreate : indexResolver.resolveIndexForClass(entity.getType())) { + createIndex(indexToCreate); + } + } + } + + protected void createIndex(IndexDefinition indexDefinition) { + mongoDbFactory.getDb().getCollection(indexDefinition.getCollection()) + .ensureIndex(indexDefinition.getIndexKeys(), indexDefinition.getIndexOptions()); + } + /** * Returns whether the current index creator was registered for the given {@link MappingContext}. * @@ -184,32 +143,4 @@ public class MongoPersistentEntityIndexCreator implements return this.mappingContext.equals(context); } - /** - * Triggers the actual index creation. - * - * @param collection the collection to create the index in - * @param name the name of the index about to be created - * @param indexDefinition the index definition - * @param unique whether it shall be a unique index - * @param dropDups whether to drop duplicates - * @param sparse sparse or not - * @param background whether the index will be created in the background - * @param expireAfterSeconds the time to live for documents in the collection - */ - protected void ensureIndex(String collection, String name, DBObject indexDefinition, boolean unique, - boolean dropDups, boolean sparse, boolean background, int expireAfterSeconds) { - - DBObject opts = new BasicDBObject(); - opts.put("name", name); - opts.put("dropDups", dropDups); - opts.put("sparse", sparse); - opts.put("unique", unique); - opts.put("background", background); - - if (expireAfterSeconds != -1) { - opts.put("expireAfterSeconds", expireAfterSeconds); - } - - mongoDbFactory.getDb().getCollection(collection).ensureIndex(indexDefinition, opts); - } } 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 new file mode 100644 index 000000000..78a94b294 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java @@ -0,0 +1,351 @@ +/* + * Copyright 2014 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.core.index; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mongodb.core.index.Index.Duplicates; +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.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * {@link IndexResolver} implementation inspecting {@link MongoPersistentEntity} for {@link MongoPersistentEntity} to be + * indexed.
+ * All {@link MongoPersistentProperty} of the {@link MongoPersistentEntity} are inspected for potential indexes by + * scanning related annotations. + * + * @author Christoph Strobl + * @since 1.5 + */ +public class MongoPersistentEntityIndexResolver implements IndexResolver { + + private final MongoMappingContext mappingContext; + + /** + * Create new {@link MongoPersistentEntityIndexResolver}. + * + * @param mappingContext must not be {@literal null}. + */ + public MongoPersistentEntityIndexResolver(MongoMappingContext mappingContext) { + + Assert.notNull(mappingContext, "Mapping context must not be null in order to resolve index definitions"); + this.mappingContext = mappingContext; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexResolver#resolveIndexForClass(java.lang.Class) + */ + @Override + public List resolveIndexForClass(Class type) { + return resolveIndexForEntity(mappingContext.getPersistentEntity(type)); + } + + /** + * Resolve the {@link IndexDefinition}s for given {@literal root} entity by traversing {@link MongoPersistentProperty} + * scanning for index annotations {@link Indexed}, {@link CompoundIndex} and {@link GeospatialIndex}. The given + * {@literal root} has therefore to be annotated with {@link Document}. + * + * @param root must not be null. + * @return List of {@link IndexDefinitionHolder}. Will never be {@code null}. + * @throws IllegalArgumentException in case of missing {@link Document} annotation marking root entities. + */ + public List resolveIndexForEntity(final MongoPersistentEntity root) { + + Assert.notNull(root, "Index cannot be resolved for given 'null' entity."); + Document document = root.findAnnotation(Document.class); + Assert.notNull(document, "Given entity is not collection root."); + + final List indexInformation = new ArrayList(); + indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType())); + + root.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) { + + if (persistentProperty.isEntity()) { + indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(), + persistentProperty.getFieldName(), root.getCollection())); + } + + IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty( + persistentProperty.getFieldName(), root.getCollection(), persistentProperty); + if (indexDefinitionHolder != null) { + indexInformation.add(indexDefinitionHolder); + } + } + }); + + return indexInformation; + } + + /** + * Recursively resolve and inspect properties of given {@literal type} for indexes to be created. + * + * @param type + * @param path The {@literal "dot} path. + * @param collection + * @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property + * types. Will never be {@code null}. + */ + private List resolveIndexForClass(Class type, final String path, final String collection) { + + final List indexInformation = new ArrayList(); + indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, type)); + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(type); + entity.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) { + + String propertyDotPath = (StringUtils.hasText(path) ? (path + ".") : "") + persistentProperty.getFieldName(); + + if (persistentProperty.isEntity()) { + indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(), propertyDotPath, collection)); + } + + IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(propertyDotPath, + collection, persistentProperty); + if (indexDefinitionHolder != null) { + indexInformation.add(indexDefinitionHolder); + } + } + }); + + return indexInformation; + } + + private IndexDefinitionHolder createIndexDefinitionHolderForProperty(String dotPath, String collection, + MongoPersistentProperty persistentProperty) { + + if (persistentProperty.isAnnotationPresent(Indexed.class)) { + return createIndexDefinition(dotPath, collection, persistentProperty); + } else if (persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class)) { + return createGeoSpatialIndexDefinition(dotPath, collection, persistentProperty); + } + + return null; + } + + private List potentiallyCreateCompoundIndexDefinitions(String dotPath, String collection, + Class type) { + + if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null) { + return Collections.emptyList(); + } + + return createCompoundIndexDefinitions(dotPath, collection, type); + } + + /** + * Create {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} for {@link CompoundIndexes} of given type. + * + * @param dotPath The properties {@literal "dot"} path representation from its document root. + * @param collection + * @param type + * @return + */ + protected List createCompoundIndexDefinitions(String dotPath, String collection, Class type) { + + CompoundIndexes indexes = AnnotationUtils.findAnnotation(type, CompoundIndexes.class); + List indexDefinitions = new ArrayList( + indexes.value().length); + + for (CompoundIndex index : indexes.value()) { + + IndexDefinitionHolder holder = new IndexDefinitionHolder(StringUtils.hasText(index.name()) ? index.name() + : dotPath); + + CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition((DBObject) JSON.parse(index.def())); + indexDefinition.named(index.name()); + indexDefinition.setCollection(StringUtils.hasText(index.collection()) ? index.collection() : collection); + 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); + } + + holder.setIndexDefinition(indexDefinition); + indexDefinitions.add(holder); + } + + return indexDefinitions; + } + + /** + * Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link Indexed} for given + * {@link MongoPersistentProperty}. + * + * @param dotPath The properties {@literal "dot"} path representation from its document root. + * @param collection + * @param persitentProperty + * @return + */ + protected IndexDefinitionHolder createIndexDefinition(String dotPath, String collection, + MongoPersistentProperty persitentProperty) { + + Indexed index = persitentProperty.findAnnotation(Indexed.class); + + IndexDefinitionHolder holder = new IndexDefinitionHolder(dotPath); + + Index indexDefinition = new Index(); + indexDefinition.setCollection(StringUtils.hasText(index.collection()) ? index.collection() : collection); + indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : persitentProperty.getFieldName()); + 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); + } + + holder.setIndexDefinition(indexDefinition); + return holder; + } + + /** + * Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link GeoSpatialIndexed} for + * {@link MongoPersistentProperty}. + * + * @param dotPath The properties {@literal "dot"} path representation from its document root. + * @param collection + * @param persistentProperty + * @return + */ + protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath, String collection, + MongoPersistentProperty persistentProperty) { + + GeoSpatialIndexed index = persistentProperty.findAnnotation(GeoSpatialIndexed.class); + + IndexDefinitionHolder holder = new IndexDefinitionHolder(dotPath); + + GeospatialIndex indexDefinition = new GeospatialIndex(dotPath); + indexDefinition.setCollection(StringUtils.hasText(index.collection()) ? index.collection() : collection); + indexDefinition.withBits(index.bits()); + indexDefinition.withMin(index.min()).withMax(index.max()); + indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : persistentProperty.getName()); + indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField()); + + holder.setIndexDefinition(indexDefinition); + return holder; + } + + /** + * Implementation of {@link IndexDefinition} holding additional (property)path information used for creating the + * index. The path itself is the properties {@literal "dot"} path representation from its root document. + * + * @author Christoph Strobl + * @since 1.5 + */ + public static class IndexDefinitionHolder implements IndexDefinition { + + private String path; + private IndexDefinition indexDefinition; + + /** + * Create + * + * @param path + */ + public IndexDefinitionHolder(String path) { + this.path = path; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexDefinition#getCollection() + */ + @Override + public String getCollection() { + return indexDefinition != null ? indexDefinition.getCollection() : null; + } + + /** + * Get the {@liteal "dot"} path used to create the index. + * + * @return + */ + public String getPath() { + return path; + } + + /** + * Get the {@literal raw} {@link IndexDefinition}. + * + * @return + */ + public IndexDefinition getIndexDefinition() { + return indexDefinition; + } + + /** + * Set the {@literal raw} {@link IndexDefinition}. + * + * @param indexDefinition + */ + public void setIndexDefinition(IndexDefinition indexDefinition) { + this.indexDefinition = indexDefinition; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys() + */ + @Override + public DBObject getIndexKeys() { + return indexDefinition.getIndexKeys(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexOptions() + */ + @Override + public DBObject getIndexOptions() { + return indexDefinition.getIndexOptions(); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java index e013ca833..c84ce35b2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java @@ -31,6 +31,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.WriteResultChecking; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; +import org.springframework.data.mongodb.core.mapping.Document; import com.mongodb.DBCollection; import com.mongodb.DBObject; @@ -43,6 +44,7 @@ import com.mongodb.WriteConcern; * @author Laurent Canet * @author Oliver Gierke * @author Thomas Darimont + * @author Christoph Strobl */ public class GeoSpatialIndexTests extends AbstractIntegrationTests { @@ -129,6 +131,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests { }); } + @Document static class GeoSpatialEntity2D { public String id; @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2D) public org.springframework.data.geo.Point location; @@ -138,6 +141,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests { } } + @Document static class GeoSpatialEntityHaystack { public String id; public String name; @@ -154,6 +158,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests { double coordinates[]; } + @Document static class GeoSpatialEntity2DSphere { public String id; @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) public GeoJsonPoint location; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java index 5555ffd3d..5ac8c4c57 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright (c) 2011-2014 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package org.springframework.data.mongodb.core.index; -import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; import org.junit.After; import org.junit.Test; @@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -38,13 +38,13 @@ import com.mongodb.MongoException; * Integration tests for index handling. * * @author Oliver Gierke + * @author Christoph Strobl */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") public class IndexingIntegrationTests { - @Autowired - MongoOperations operations; + @Autowired MongoOperations operations; @After public void tearDown() { @@ -61,11 +61,10 @@ public class IndexingIntegrationTests { assertThat(hasIndex("_firstname", IndexedPerson.class), is(true)); } + @Document class IndexedPerson { - @Field("_firstname") - @Indexed - String firstname; + @Field("_firstname") @Indexed String firstname; } /** 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 885f77d57..a5591f73d 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-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -21,18 +21,27 @@ import static org.junit.Assert.*; import java.util.Collections; import java.util.Date; +import org.hamcrest.core.IsEqual; +import org.junit.Before; 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.data.geo.Point; import org.springframework.data.mapping.context.MappingContextEvent; import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DB; +import com.mongodb.DBCollection; import com.mongodb.DBObject; /** @@ -41,24 +50,45 @@ import com.mongodb.DBObject; * @author Oliver Gierke * @author Philipp Schneider * @author Johno Crawford + * @author Christoph Strobl */ @RunWith(MockitoJUnitRunner.class) public class MongoPersistentEntityIndexCreatorUnitTests { - @Mock MongoDbFactory factory; - @Mock ApplicationContext context; + private @Mock MongoDbFactory factory; + private @Mock ApplicationContext context; + private @Mock DB db; + private @Mock DBCollection collection; + + ArgumentCaptor keysCaptor; + ArgumentCaptor optionsCaptor; + ArgumentCaptor collectionCaptor; + + @Before + public void setUp() { + + keysCaptor = ArgumentCaptor.forClass(DBObject.class); + optionsCaptor = ArgumentCaptor.forClass(DBObject.class); + collectionCaptor = ArgumentCaptor.forClass(String.class); + + Mockito.when(factory.getDb()).thenReturn(db); + Mockito.when(db.getCollection(collectionCaptor.capture())).thenReturn(collection); + + Mockito.doNothing().when(collection).ensureIndex(keysCaptor.capture(), optionsCaptor.capture()); + } @Test public void buildsIndexDefinitionUsingFieldName() { MongoMappingContext mappingContext = prepareMappingContext(Person.class); - DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); - assertThat(creator.indexDefinition, is(notNullValue())); - assertThat(creator.indexDefinition.keySet(), hasItem("fieldname")); - assertThat(creator.name, is("indexName")); - assertThat(creator.background, is(false)); - assertThat(creator.expireAfterSeconds, is(-1)); + new MongoPersistentEntityIndexCreator(mappingContext, factory); + + assertThat(keysCaptor.getValue(), is(notNullValue())); + assertThat(keysCaptor.getValue().keySet(), hasItem("fieldname")); + assertThat(optionsCaptor.getValue().get("name").toString(), is("indexName")); + assertThat(optionsCaptor.getValue().get("background"), nullValue()); + assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue()); } @Test @@ -67,7 +97,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests { MongoMappingContext mappingContext = new MongoMappingContext(); MongoMappingContext personMappingContext = prepareMappingContext(Person.class); - DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); + MongoPersistentEntityIndexCreator creator = new MongoPersistentEntityIndexCreator(mappingContext, factory); MongoPersistentEntity entity = personMappingContext.getPersistentEntity(Person.class); MappingContextEvent, MongoPersistentProperty> event = new MappingContextEvent, MongoPersistentProperty>( @@ -75,7 +105,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests { creator.onApplicationEvent(event); - assertThat(creator.indexDefinition, is(nullValue())); + Mockito.verifyZeroInteractions(collection); } /** @@ -87,7 +117,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests { MongoMappingContext mappingContext = new MongoMappingContext(); mappingContext.initialize(); - MongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); + MongoPersistentEntityIndexCreator creator = new MongoPersistentEntityIndexCreator(mappingContext, factory); assertThat(creator.isIndexCreatorFor(mappingContext), is(true)); assertThat(creator.isIndexCreatorFor(new MongoMappingContext()), is(false)); } @@ -99,12 +129,13 @@ public class MongoPersistentEntityIndexCreatorUnitTests { public void triggersBackgroundIndexingIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class); - DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); + new MongoPersistentEntityIndexCreator(mappingContext, factory); - assertThat(creator.indexDefinition, is(notNullValue())); - assertThat(creator.indexDefinition.keySet(), hasItem("lastname")); - assertThat(creator.name, is("lastname")); - assertThat(creator.background, is(true)); + assertThat(keysCaptor.getValue(), is(notNullValue())); + assertThat(keysCaptor.getValue().keySet(), hasItem("lastname")); + assertThat(optionsCaptor.getValue().get("name").toString(), is("lastname")); + assertThat(optionsCaptor.getValue().get("background"), IsEqual. equalTo(true)); + assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue()); } /** @@ -114,11 +145,25 @@ public class MongoPersistentEntityIndexCreatorUnitTests { public void expireAfterSecondsIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(Milk.class); - DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); + new MongoPersistentEntityIndexCreator(mappingContext, factory); - assertThat(creator.indexDefinition, is(notNullValue())); - assertThat(creator.indexDefinition.keySet(), hasItem("expiry")); - assertThat(creator.expireAfterSeconds, is(60)); + assertThat(keysCaptor.getValue(), is(notNullValue())); + assertThat(keysCaptor.getValue().keySet(), hasItem("expiry")); + assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), IsEqual. equalTo(60L)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void createsNotNestedGeoSpatialIndexCorrectly() { + + MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class); + new MongoPersistentEntityIndexCreator(mappingContext, factory); + + assertThat(keysCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("company.address.location", "2d").get())); + assertThat(optionsCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("name", "location").add("min", -180) + .add("max", 180).add("bits", 26).get())); } private static MongoMappingContext prepareMappingContext(Class type) { @@ -130,41 +175,46 @@ public class MongoPersistentEntityIndexCreatorUnitTests { return mappingContext; } + @Document static class Person { - @Indexed(name = "indexName") @Field("fieldname") String field; + @Indexed(name = "indexName")// + @Field("fieldname")// + String field; } + @Document static class AnotherPerson { @Indexed(background = true) String lastname; } + @Document static class Milk { @Indexed(expireAfterSeconds = 60) Date expiry; } - static class DummyMongoPersistentEntityIndexCreator extends MongoPersistentEntityIndexCreator { + @Document + static class Wrapper { + + String id; + Company company; + + } + + static class Company { - DBObject indexDefinition; String name; - boolean background; - int expireAfterSeconds; + Address address; + } - public DummyMongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) { - super(mappingContext, mongoDbFactory); - } + static class Address { - @Override - protected void ensureIndex(String collection, String name, DBObject indexDefinition, boolean unique, - boolean dropDups, boolean sparse, boolean background, int expireAfterSeconds) { + String street; + String city; - this.name = name; - this.indexDefinition = indexDefinition; - this.background = background; - this.expireAfterSeconds = expireAfterSeconds; - } + @GeoSpatialIndexed Point location; } } 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 new file mode 100644 index 000000000..162beadac --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java @@ -0,0 +1,390 @@ +/* + * Copyright 2014 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.core.index; + +import static org.hamcrest.collection.IsCollectionWithSize.*; +import static org.hamcrest.collection.IsEmptyCollection.*; +import static org.hamcrest.core.IsEqual.*; +import static org.hamcrest.core.IsInstanceOf.*; +import static org.junit.Assert.*; + +import java.util.Collections; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.CompoundIndexResolutionTests; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.GeoSpatialIndexResolutionTests; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.IndexResolutionTests; +import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.MixedIndexResolutionTests; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; + +import com.mongodb.BasicDBObjectBuilder; + +/** + * @author Christoph Strobl + */ +@RunWith(Suite.class) +@SuiteClasses({ IndexResolutionTests.class, GeoSpatialIndexResolutionTests.class, CompoundIndexResolutionTests.class, + MixedIndexResolutionTests.class }) +public class MongoPersistentEntityIndexResolverUnitTests { + + /** + * Test resolution of {@link Indexed}. + * + * @author Christoph Strobl + */ + public static class IndexResolutionTests { + + /** + * @see DATAMONGO-899 + */ + @Test + public void indexPathOnLevelZeroIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelZero.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("indexedProperty", "Zero", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void indexPathOnLevelOneIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOne.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("zero.indexedProperty", "One", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void depplyNestedIndexPathIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelTwo.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("one.zero.indexedProperty", "Two", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void resolvesIndexPathNameForNamedPropertiesCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOneWithExplicitlyNamedField.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("customZero.customFieldName", "indexOnLevelOneWithExplicitlyNamedField", + indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void resolvesIndexDefinitionCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelZero.class); + + IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition(); + assertThat(indexDefinition.getIndexOptions(), equalTo(new BasicDBObjectBuilder().add("name", "indexedProperty") + .get())); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void resolvesIndexDefinitionOptionsCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(WithOptionsOnIndexedProperty.class); + + IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition(); + assertThat(indexDefinition.getIndexOptions(), + equalTo(new BasicDBObjectBuilder().add("name", "indexedProperty").add("unique", true).add("dropDups", true) + .add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get())); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void resolvesIndexCollectionNameCorrectlyWhenDefinedInAnnotation() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(WithOptionsOnIndexedProperty.class); + assertThat(indexDefinitions.get(0).getCollection(), equalTo("CollectionOverride")); + } + + @Document(collection = "Zero") + static class IndexOnLevelZero { + @Indexed String indexedProperty; + } + + @Document(collection = "One") + static class IndexOnLevelOne { + IndexOnLevelZero zero; + } + + @Document(collection = "Two") + static class IndexOnLevelTwo { + IndexOnLevelOne one; + } + + @Document(collection = "WithOptionsOnIndexedProperty") + static class WithOptionsOnIndexedProperty { + + @Indexed(background = true, collection = "CollectionOverride", direction = IndexDirection.DESCENDING, + dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true)// + String indexedProperty; + } + + @Document + static class IndexOnLevelOneWithExplicitlyNamedField { + + @Field("customZero") IndexOnLevelZeroWithExplicityNamedField zero; + } + + static class IndexOnLevelZeroWithExplicityNamedField { + + @Indexed @Field("customFieldName") String namedProperty; + } + + } + + /** + * Test resolution of {@link GeoSpatialIndexed}. + * + * @author Christoph Strobl + */ + public static class GeoSpatialIndexResolutionTests { + + /** + * @see DATAMONGO-899 + */ + @Test + public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelZero.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("geoIndexedProperty", "Zero", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelOne.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("zero.geoIndexedProperty", "One", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelTwo.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("one.zero.geoIndexedProperty", "Two", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void resolvesIndexDefinitionOptionsCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(WithOptionsOnGeoSpatialIndexProperty.class); + + IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition(); + + assertThat( + indexDefinition.getIndexOptions(), + equalTo(new BasicDBObjectBuilder().add("name", "location").add("min", 1).add("max", 100).add("bits", 2).get())); + } + + @Document(collection = "Zero") + static class GeoSpatialIndexOnLevelZero { + @GeoSpatialIndexed Point geoIndexedProperty; + } + + @Document(collection = "One") + static class GeoSpatialIndexOnLevelOne { + GeoSpatialIndexOnLevelZero zero; + } + + @Document(collection = "Two") + static class GeoSpatialIndexOnLevelTwo { + GeoSpatialIndexOnLevelOne one; + } + + @Document(collection = "WithOptionsOnGeoSpatialIndexProperty") + static class WithOptionsOnGeoSpatialIndexProperty { + + @GeoSpatialIndexed(collection = "CollectionOverride", bits = 2, max = 100, min = 1, + type = GeoSpatialIndexType.GEO_2D)// + Point location; + } + + } + + /** + * Test resolution of {@link CompoundIndexes}. + * + * @author Christoph Strobl + */ + public static class CompoundIndexResolutionTests { + + /** + * @see DATAMONGO-899 + */ + @Test + public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class); + + assertThat(indexDefinitions, hasSize(1)); + assertIndexPathAndCollection("compound_index", "CompoundIndexOnLevelZero", indexDefinitions.get(0)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void compoundIndexOptionsResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class); + + IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition(); + assertThat(indexDefinition.getIndexOptions(), + equalTo(new BasicDBObjectBuilder().add("name", "compound_index").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())); + } + + @Document(collection = "CompoundIndexOnLevelZero") + @CompoundIndexes({ @CompoundIndex(name = "compound_index", def = "{'foo': 1, 'bar': -1}", background = true, + dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true) }) + static class CompoundIndexOnLevelZero {} + } + + public static class MixedIndexResolutionTests { + + /** + * @see DATAMONGO-899 + */ + @Test + public void multipleIndexesResolvedCorrectly() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(MixedIndexRoot.class); + + assertThat(indexDefinitions, hasSize(2)); + assertThat(indexDefinitions.get(0).getIndexDefinition(), instanceOf(Index.class)); + assertThat(indexDefinitions.get(1).getIndexDefinition(), instanceOf(GeospatialIndex.class)); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(Inner.class); + assertThat(indexDefinitions, hasSize(1)); + assertThat(indexDefinitions.get(0).getIndexDefinition().getCollection(), equalTo("inner")); + assertThat(indexDefinitions.get(0).getIndexDefinition().getIndexKeys(), + equalTo(new BasicDBObjectBuilder().add("outer", 1).get())); + } + + /** + * @see DATAMONGO-899 + */ + @Test + public void associationsShouldNotBeTraversed() { + + List indexDefinitions = prepareMappingContextAndResolveIndexForType(Outer.class); + assertThat(indexDefinitions, empty()); + } + + @Document + static class MixedIndexRoot { + + @Indexed String first; + NestedGeoIndex nestedGeo; + } + + static class NestedGeoIndex { + + @GeoSpatialIndexed Point location; + } + + @Document + static class Outer { + + @DBRef Inner inner; + } + + @Document + static class Inner { + + @Indexed Outer outer; + } + + } + + private static List prepareMappingContextAndResolveIndexForType(Class type) { + + MongoMappingContext mappingContext = prepareMappingContext(type); + MongoPersistentEntityIndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext); + return resolver.resolveIndexForEntity(mappingContext.getPersistentEntity(type)); + } + + private static MongoMappingContext prepareMappingContext(Class type) { + + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setInitialEntitySet(Collections.singleton(type)); + mappingContext.initialize(); + + return mappingContext; + } + + private static void assertIndexPathAndCollection(String expectedPath, String expectedCollection, + IndexDefinitionHolder holder) { + + assertThat(holder.getPath(), equalTo(expectedPath)); + assertThat(holder.getCollection(), equalTo(expectedCollection)); + } +} diff --git a/src/docbkx/reference/mapping.xml b/src/docbkx/reference/mapping.xml index 775df2286..91675e4d6 100644 --- a/src/docbkx/reference/mapping.xml +++ b/src/docbkx/reference/mapping.xml @@ -312,6 +312,11 @@ public class Person { document, making searches faster. + + Automatic index creation is only done for types annotated with + @Document. + +
Mapping annotation overview