Browse Source

DATAMONGO-899 - Ensure proper creation of index structures for nested entities.

Index creation did not consider the properties path when creating the index. This lead to broken index creation when nesting entities that might require index structures.

Off now index creation traverses the entities property path for all top level entities (namely those holding the @Document annotation) and creates the index using the full property path.

This is a breaking change as having an entity to carry the @Document annotation has not been required by now.

Original Pull Request: #168
pull/95/merge
Christoph Strobl 12 years ago committed by Oliver Gierke
parent
commit
7848da63f2
  1. 42
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndexDefinition.java
  2. 21
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java
  3. 77
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java
  4. 10
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexDefinition.java
  5. 35
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexResolver.java
  6. 141
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java
  7. 351
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java
  8. 5
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java
  9. 15
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java
  10. 130
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java
  11. 390
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java
  12. 5
      src/docbkx/reference/mapping.xml

42
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;
}
}

21
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Jon Brisbin
* @author Oliver Gierke * @author Oliver Gierke
* @author Laurent Canet * @author Laurent Canet
* @author Christoph Strobl
*/ */
public class GeospatialIndex implements IndexDefinition { public class GeospatialIndex implements IndexDefinition {
@ -38,6 +39,7 @@ public class GeospatialIndex implements IndexDefinition {
private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D; private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D;
private Double bucketSize = 1.0; private Double bucketSize = 1.0;
private String additionalField; private String additionalField;
private String collection;
/** /**
* Creates a new {@link GeospatialIndex} for the given field. * Creates a new {@link GeospatialIndex} for the given field.
@ -120,6 +122,23 @@ public class GeospatialIndex implements IndexDefinition {
return this; 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() { public DBObject getIndexKeys() {
DBObject dbo = new BasicDBObject(); DBObject dbo = new BasicDBObject();

77
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.query.Order; import org.springframework.data.mongodb.core.query.Order;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.DBObject; import com.mongodb.DBObject;
/**
* @author Oliver Gierke
* @author Christoph Strobl
*/
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class Index implements IndexDefinition { public class Index implements IndexDefinition {
@ -41,6 +47,12 @@ public class Index implements IndexDefinition {
private boolean sparse = false; private boolean sparse = false;
private boolean background = false;
private long expire = -1;
private String collection;
public Index() {} public Index() {}
public Index(String key, Direction direction) { public Index(String key, Direction direction) {
@ -104,6 +116,61 @@ public class Index implements IndexDefinition {
return this; 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 * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping
* @param duplicates * @param duplicates
@ -125,9 +192,11 @@ public class Index implements IndexDefinition {
} }
public DBObject getIndexOptions() { public DBObject getIndexOptions() {
if (name == null && !unique) { if (name == null && !unique) {
return null; return null;
} }
DBObject dbo = new BasicDBObject(); DBObject dbo = new BasicDBObject();
if (name != null) { if (name != null) {
dbo.put("name", name); dbo.put("name", name);
@ -141,6 +210,12 @@ public class Index implements IndexDefinition {
if (sparse) { if (sparse) {
dbo.put("sparse", true); dbo.put("sparse", true);
} }
if (background) {
dbo.put("background", true);
}
if (expire >= 0) {
dbo.put("expireAfterSeconds", expire);
}
return dbo; return dbo;
} }

10
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 <jbrisbin@vmware.com> * @author Jon Brisbin <jbrisbin@vmware.com>
* @author Christoph Strobl
*/ */
public interface IndexDefinition { public interface IndexDefinition {
@ -27,4 +28,11 @@ public interface IndexDefinition {
DBObject getIndexOptions(); DBObject getIndexOptions();
/**
* Get the collection name for the index.
*
* @return
* @since 1.5
*/
String getCollection();
} }

35
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<? extends IndexDefinition> resolveIndexForClass(Class<?> type);
}

141
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.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.data.mapping.PersistentEntity; 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.MappingContext;
import org.springframework.data.mapping.context.MappingContextEvent; import org.springframework.data.mapping.context.MappingContextEvent;
import org.springframework.data.mongodb.MongoDbFactory; 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.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.util.Assert; 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} * 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 Philipp Schneider
* @author Johno Crawford * @author Johno Crawford
* @author Laurent Canet * @author Laurent Canet
* @author Christoph Strobl
*/ */
public class MongoPersistentEntityIndexCreator implements public class MongoPersistentEntityIndexCreator implements
ApplicationListener<MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>> { ApplicationListener<MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>> {
@ -54,21 +50,37 @@ public class MongoPersistentEntityIndexCreator implements
private final Map<Class<?>, Boolean> classesSeen = new ConcurrentHashMap<Class<?>, Boolean>(); private final Map<Class<?>, Boolean> classesSeen = new ConcurrentHashMap<Class<?>, Boolean>();
private final MongoDbFactory mongoDbFactory; private final MongoDbFactory mongoDbFactory;
private final MongoMappingContext mappingContext; private final MongoMappingContext mappingContext;
private final IndexResolver indexResolver;
/** /**
* Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and * Creats a new {@link MongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext} and
* {@link MongoDbFactory}. * {@link MongoDbFactory}.
* *
* @param mappingContext must not be {@literal null} * @param mappingContext must not be {@literal null}.
* @param mongoDbFactory must not be {@literal null} * @param mongoDbFactory must not be {@literal null}.
*/ */
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) { 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(mongoDbFactory);
Assert.notNull(mappingContext); Assert.notNull(mappingContext);
Assert.notNull(indexResolver);
this.mongoDbFactory = mongoDbFactory; this.mongoDbFactory = mongoDbFactory;
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
this.indexResolver = indexResolver;
for (MongoPersistentEntity<?> entity : mappingContext.getPersistentEntities()) { for (MongoPersistentEntity<?> entity : mappingContext.getPersistentEntities()) {
checkForIndexes(entity); 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(); final Class<?> type = entity.getType();
if (!classesSeen.containsKey(type)) { if (!classesSeen.containsKey(type)) {
this.classesSeen.put(type, Boolean.TRUE);
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Analyzing class " + type + " for index information."); LOGGER.debug("Analyzing class " + type + " for index information.");
} }
// Make sure indexes get created checkForAndCreateIndexes(entity);
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(), protected void checkForAndCreateIndexes(MongoPersistentEntity<?> entity) {
index.background(), index.expireAfterSeconds());
if (LOGGER.isDebugEnabled()) { if (entity.findAnnotation(Document.class) != null) {
LOGGER.debug("Created compound index " + index); for (IndexDefinition indexToCreate : indexResolver.resolveIndexForClass(entity.getType())) {
} createIndex(indexToCreate);
}
} }
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
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);
} }
} }
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}. * 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); 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);
}
} }

351
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. <br />
* 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<IndexDefinitionHolder> 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<IndexDefinitionHolder> 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<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType()));
root.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@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<IndexDefinitionHolder> resolveIndexForClass(Class<?> type, final String path, final String collection) {
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, type));
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@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<IndexDefinitionHolder> 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<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String collection, Class<?> type) {
CompoundIndexes indexes = AnnotationUtils.findAnnotation(type, CompoundIndexes.class);
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>(
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();
}
}
}

5
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.WriteResultChecking;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
import com.mongodb.DBCollection; import com.mongodb.DBCollection;
import com.mongodb.DBObject; import com.mongodb.DBObject;
@ -43,6 +44,7 @@ import com.mongodb.WriteConcern;
* @author Laurent Canet * @author Laurent Canet
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
* @author Christoph Strobl
*/ */
public class GeoSpatialIndexTests extends AbstractIntegrationTests { public class GeoSpatialIndexTests extends AbstractIntegrationTests {
@ -129,6 +131,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests {
}); });
} }
@Document
static class GeoSpatialEntity2D { static class GeoSpatialEntity2D {
public String id; public String id;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2D) public org.springframework.data.geo.Point location; @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 { static class GeoSpatialEntityHaystack {
public String id; public String id;
public String name; public String name;
@ -154,6 +158,7 @@ public class GeoSpatialIndexTests extends AbstractIntegrationTests {
double coordinates[]; double coordinates[];
} }
@Document
static class GeoSpatialEntity2DSphere { static class GeoSpatialEntity2DSphere {
public String id; public String id;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) public GeoJsonPoint location; @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) public GeoJsonPoint location;

15
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; package org.springframework.data.mongodb.core.index;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoOperations; 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.data.mongodb.core.mapping.Field;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ -38,13 +38,13 @@ import com.mongodb.MongoException;
* Integration tests for index handling. * Integration tests for index handling.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml") @ContextConfiguration("classpath:infrastructure.xml")
public class IndexingIntegrationTests { public class IndexingIntegrationTests {
@Autowired @Autowired MongoOperations operations;
MongoOperations operations;
@After @After
public void tearDown() { public void tearDown() {
@ -61,11 +61,10 @@ public class IndexingIntegrationTests {
assertThat(hasIndex("_firstname", IndexedPerson.class), is(true)); assertThat(hasIndex("_firstname", IndexedPerson.class), is(true));
} }
@Document
class IndexedPerson { class IndexedPerson {
@Field("_firstname") @Field("_firstname") @Indexed String firstname;
@Indexed
String firstname;
} }
/** /**

130
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Collections;
import java.util.Date; import java.util.Date;
import org.hamcrest.core.IsEqual;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.context.MappingContextEvent; import org.springframework.data.mapping.context.MappingContextEvent;
import org.springframework.data.mongodb.MongoDbFactory; 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.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject; import com.mongodb.DBObject;
/** /**
@ -41,24 +50,45 @@ import com.mongodb.DBObject;
* @author Oliver Gierke * @author Oliver Gierke
* @author Philipp Schneider * @author Philipp Schneider
* @author Johno Crawford * @author Johno Crawford
* @author Christoph Strobl
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class MongoPersistentEntityIndexCreatorUnitTests { public class MongoPersistentEntityIndexCreatorUnitTests {
@Mock MongoDbFactory factory; private @Mock MongoDbFactory factory;
@Mock ApplicationContext context; private @Mock ApplicationContext context;
private @Mock DB db;
private @Mock DBCollection collection;
ArgumentCaptor<DBObject> keysCaptor;
ArgumentCaptor<DBObject> optionsCaptor;
ArgumentCaptor<String> 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 @Test
public void buildsIndexDefinitionUsingFieldName() { public void buildsIndexDefinitionUsingFieldName() {
MongoMappingContext mappingContext = prepareMappingContext(Person.class); MongoMappingContext mappingContext = prepareMappingContext(Person.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.indexDefinition, is(notNullValue())); new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.indexDefinition.keySet(), hasItem("fieldname"));
assertThat(creator.name, is("indexName")); assertThat(keysCaptor.getValue(), is(notNullValue()));
assertThat(creator.background, is(false)); assertThat(keysCaptor.getValue().keySet(), hasItem("fieldname"));
assertThat(creator.expireAfterSeconds, is(-1)); assertThat(optionsCaptor.getValue().get("name").toString(), is("indexName"));
assertThat(optionsCaptor.getValue().get("background"), nullValue());
assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue());
} }
@Test @Test
@ -67,7 +97,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
MongoMappingContext mappingContext = new MongoMappingContext(); MongoMappingContext mappingContext = new MongoMappingContext();
MongoMappingContext personMappingContext = prepareMappingContext(Person.class); MongoMappingContext personMappingContext = prepareMappingContext(Person.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); MongoPersistentEntityIndexCreator creator = new MongoPersistentEntityIndexCreator(mappingContext, factory);
MongoPersistentEntity<?> entity = personMappingContext.getPersistentEntity(Person.class); MongoPersistentEntity<?> entity = personMappingContext.getPersistentEntity(Person.class);
MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty> event = new MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>( MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty> event = new MappingContextEvent<MongoPersistentEntity<?>, MongoPersistentProperty>(
@ -75,7 +105,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
creator.onApplicationEvent(event); creator.onApplicationEvent(event);
assertThat(creator.indexDefinition, is(nullValue())); Mockito.verifyZeroInteractions(collection);
} }
/** /**
@ -87,7 +117,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
MongoMappingContext mappingContext = new MongoMappingContext(); MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.initialize(); mappingContext.initialize();
MongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); MongoPersistentEntityIndexCreator creator = new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.isIndexCreatorFor(mappingContext), is(true)); assertThat(creator.isIndexCreatorFor(mappingContext), is(true));
assertThat(creator.isIndexCreatorFor(new MongoMappingContext()), is(false)); assertThat(creator.isIndexCreatorFor(new MongoMappingContext()), is(false));
} }
@ -99,12 +129,13 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
public void triggersBackgroundIndexingIfConfigured() { public void triggersBackgroundIndexingIfConfigured() {
MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class); MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.indexDefinition, is(notNullValue())); assertThat(keysCaptor.getValue(), is(notNullValue()));
assertThat(creator.indexDefinition.keySet(), hasItem("lastname")); assertThat(keysCaptor.getValue().keySet(), hasItem("lastname"));
assertThat(creator.name, is("lastname")); assertThat(optionsCaptor.getValue().get("name").toString(), is("lastname"));
assertThat(creator.background, is(true)); assertThat(optionsCaptor.getValue().get("background"), IsEqual.<Object> equalTo(true));
assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue());
} }
/** /**
@ -114,11 +145,25 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
public void expireAfterSecondsIfConfigured() { public void expireAfterSecondsIfConfigured() {
MongoMappingContext mappingContext = prepareMappingContext(Milk.class); MongoMappingContext mappingContext = prepareMappingContext(Milk.class);
DummyMongoPersistentEntityIndexCreator creator = new DummyMongoPersistentEntityIndexCreator(mappingContext, factory); new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(keysCaptor.getValue(), is(notNullValue()));
assertThat(keysCaptor.getValue().keySet(), hasItem("expiry"));
assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), IsEqual.<Object> equalTo(60L));
}
/**
* @see DATAMONGO-899
*/
@Test
public void createsNotNestedGeoSpatialIndexCorrectly() {
MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
assertThat(creator.indexDefinition, is(notNullValue())); assertThat(keysCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("company.address.location", "2d").get()));
assertThat(creator.indexDefinition.keySet(), hasItem("expiry")); assertThat(optionsCaptor.getValue(), equalTo(new BasicDBObjectBuilder().add("name", "location").add("min", -180)
assertThat(creator.expireAfterSeconds, is(60)); .add("max", 180).add("bits", 26).get()));
} }
private static MongoMappingContext prepareMappingContext(Class<?> type) { private static MongoMappingContext prepareMappingContext(Class<?> type) {
@ -130,41 +175,46 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
return mappingContext; return mappingContext;
} }
@Document
static class Person { static class Person {
@Indexed(name = "indexName") @Field("fieldname") String field; @Indexed(name = "indexName")//
@Field("fieldname")//
String field;
} }
@Document
static class AnotherPerson { static class AnotherPerson {
@Indexed(background = true) String lastname; @Indexed(background = true) String lastname;
} }
@Document
static class Milk { static class Milk {
@Indexed(expireAfterSeconds = 60) Date expiry; @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; String name;
boolean background; Address address;
int expireAfterSeconds; }
public DummyMongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) { static class Address {
super(mappingContext, mongoDbFactory);
} String street;
String city;
@Override
protected void ensureIndex(String collection, String name, DBObject indexDefinition, boolean unique, @GeoSpatialIndexed Point location;
boolean dropDups, boolean sparse, boolean background, int expireAfterSeconds) {
this.name = name;
this.indexDefinition = indexDefinition;
this.background = background;
this.expireAfterSeconds = expireAfterSeconds;
}
} }
} }

390
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<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelZero.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("indexedProperty", "Zero", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void indexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOne.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("zero.indexedProperty", "One", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void depplyNestedIndexPathIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelTwo.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("one.zero.indexedProperty", "Two", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexPathNameForNamedPropertiesCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOneWithExplicitlyNamedField.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("customZero.customFieldName", "indexOnLevelOneWithExplicitlyNamedField",
indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexDefinitionCorrectly() {
List<IndexDefinitionHolder> 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<IndexDefinitionHolder> 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<IndexDefinitionHolder> 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<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelZero.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("geoIndexedProperty", "Zero", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelOne.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("zero.geoIndexedProperty", "One", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(GeoSpatialIndexOnLevelTwo.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("one.zero.geoIndexedProperty", "Two", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void resolvesIndexDefinitionOptionsCorrectly() {
List<IndexDefinitionHolder> 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<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("compound_index", "CompoundIndexOnLevelZero", indexDefinitions.get(0));
}
/**
* @see DATAMONGO-899
*/
@Test
public void compoundIndexOptionsResolvedCorrectly() {
List<IndexDefinitionHolder> 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<IndexDefinitionHolder> 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<IndexDefinitionHolder> 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<IndexDefinitionHolder> 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<IndexDefinitionHolder> 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));
}
}

5
src/docbkx/reference/mapping.xml

@ -312,6 +312,11 @@ public class Person {
document, making searches faster.</para> document, making searches faster.</para>
</important> </important>
<important>
<para>Automatic index creation is only done for types annotated with
<classname>@Document</classname>.</para>
</important>
<section id="mapping-usage-annotations"> <section id="mapping-usage-annotations">
<title>Mapping annotation overview</title> <title>Mapping annotation overview</title>

Loading…
Cancel
Save