Compare commits

..

12 Commits

Author SHA1 Message Date
Spring Buildmaster
c7e65cbc40 DATAMONGO-936 - Release version 1.5.0.RELEASE. 2014-05-20 09:35:35 -07:00
Oliver Gierke
b8e02efb04 DATAMONGO-936 - Prepare 1.5 GA.
Removed obsolete readme.txt.
2014-05-20 17:14:04 +02:00
Oliver Gierke
c7f20fb836 DATAMONGO-936 - Prepare 1.5 GA. 2014-05-20 16:57:34 +02:00
Oliver Gierke
28a0202ef4 DATAMONGO-936 - Updated changelog. 2014-05-20 16:26:20 +02:00
Christoph Strobl
164e947045 DATAMONGO-926 - Avoid StackOverflowError while resolving index structures.
We now guard cyclic non transient, non DBRef property references while inspecting domain types for potentially index structures. To do so we check on the properties path and owning type to determine potential cycles. If found we log a warn message pointing to path, entity and property potentially causing problems and skip processing for this path.

Original pull request: #180.
2014-05-19 19:09:40 +02:00
Christoph Strobl
7e65c0c87d DATAMONGO-367 - Nested @Indexed should not trigger creation of separate collection.
The issue has been solved along with DATAMONGO-888 (Pull Request: #162). We have created additional tests to explicitly check it has truly been fixed.
2014-05-19 17:19:28 +02:00
Christoph Strobl
9c1f753f17 DATAMONGO-929 - Use property path for keys of Indexed and CompoundIndex.
Index creation failed for @Indexed and @CompoundIndex as the resolved dotPath was not used for creation. We now not only resolve the dotPath but also use it within the key for index definition. In case of a nested compound index the key definition is enhanced by the provided path.

When leaving the key definition empty for nested compound index we'll create an index for the whole nested document. Trying to create a compound index on root level not providing key information leads to InvalidDataApiUsageException.

Original pull request: #179.
2014-05-19 17:14:36 +02:00
Oliver Gierke
0f821eb52d DATAMONGO-925, DATAMONGO-928 - Polishing.
Only reject attribute setup if abbreviation is activated and a custom strategy is configured. Additional test cases for the rejection case and a custom, over-configuration (explicitly setting abbreviation to false, which is the default anyway).

Related pull request: #177.
2014-05-19 17:07:50 +02:00
Ryan Tenney
e3aadd63ab DATAMONGO-928 - Removed explicit default value for abbreviate-field-names from namespace XSD.
The default for boolean attributes leaks into the evaluation of XML namespace attributes which causes us being unable to detect whether two attributes have been set in a conflicting way.

Fix the documentation on the field-naming-strategy-ref attribute.

Original pull request: #183.
Related pull request: #177.
Related ticket: DATAMONGO-925.
2014-05-19 16:44:03 +02:00
Christoph Strobl
aa06d520df DATAMONGO-647 - Added test case to show that field names are mapped correctly.
Additional test added to check if the issue has truly been resolved by DATAMONGO-888.

Original pull request: #181.
Related pull Request: #162.
Related ticket: DATAMONGO-888.
2014-05-19 14:38:06 +02:00
Oliver Gierke
4fa1d4ba97 DATAMONGO-919 - After release cleanups. 2014-05-02 15:45:06 +02:00
Spring Buildmaster
ba9f11b345 DATAMONGO-919 - Prepare next development iteration. 2014-05-02 05:58:16 -07:00
19 changed files with 514 additions and 88 deletions

View File

@@ -26,7 +26,7 @@ Add the Maven dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.4.1.RELEASE</version>
<version>1.5.0.RELEASE</version>
</dependency>
```
@@ -36,7 +36,7 @@ If you'd rather like the latest snapshots of the upcoming major version, use our
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.5.0.BUILD-SNAPSHOT</version>
<version>1.6.0.BUILD-SNAPSHOT</version>
</dependency>
<repository>

10
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.5.0.RC1</version>
<version>1.5.0.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>1.4.0.RC1</version>
<version>1.4.0.RELEASE</version>
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
</parent>
@@ -29,7 +29,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>1.8.0.RC1</springdata.commons>
<springdata.commons>1.8.0.RELEASE</springdata.commons>
<mongo>2.12.1</mongo>
</properties>
@@ -122,8 +122,8 @@
<repositories>
<repository>
<id>spring-libs-milestone</id>
<url>http://repo.spring.io/libs-milestone</url>
<id>spring-libs-release</id>
<url>http://repo.spring.io/libs-release</url>
</repository>
</repositories>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.5.0.RC1</version>
<version>1.5.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -48,7 +48,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.5.0.RC1</version>
<version>1.5.0.RELEASE</version>
</dependency>
<dependency>

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.5.0.RC1</version>
<version>1.5.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.5.0.RC1</version>
<version>1.5.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.5.0.RC1</version>
<version>1.5.0.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -228,11 +228,13 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
String abbreviateFieldNames = element.getAttribute("abbreviate-field-names");
String fieldNamingStrategy = element.getAttribute("field-naming-strategy-ref");
if (StringUtils.hasText(fieldNamingStrategy) && StringUtils.hasText(abbreviateFieldNames)) {
boolean fieldNamingStrategyReferenced = StringUtils.hasText(fieldNamingStrategy);
boolean abbreviationActivated = StringUtils.hasText(abbreviateFieldNames)
&& Boolean.parseBoolean(abbreviateFieldNames);
context
.error("Only one of the attributes abbreviate-field-names and field-naming-strategy-ref can be configured!",
element);
if (fieldNamingStrategyReferenced && abbreviationActivated) {
context.error("Field name abbreviation cannot be activated if a field-naming-strategy-ref is configured!",
element);
return;
}
@@ -240,7 +242,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
if ("true".equals(abbreviateFieldNames)) {
value = new RootBeanDefinition(CamelCaseAbbreviatingFieldNamingStrategy.class);
} else if (StringUtils.hasText(fieldNamingStrategy)) {
} else if (fieldNamingStrategyReferenced) {
value = new RuntimeBeanReference(fieldNamingStrategy);
}

View File

@@ -36,11 +36,12 @@ public @interface CompoundIndex {
/**
* The actual index definition in JSON format. The keys of the JSON document are the fields to be indexed, the values
* define the index direction (1 for ascending, -1 for descending).
* define the index direction (1 for ascending, -1 for descending). <br />
* If left empty on nested document, the whole document will be indexed.
*
* @return
*/
String def();
String def() default "";
/**
* It does not actually make sense to use that attribute as the direction has to be defined in the {@link #def()}

View File

@@ -17,10 +17,17 @@ package org.springframework.data.mongodb.core.index;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mongodb.core.index.Index.Duplicates;
@@ -31,6 +38,8 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
@@ -45,6 +54,8 @@ import com.mongodb.util.JSON;
*/
public class MongoPersistentEntityIndexResolver implements IndexResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoPersistentEntityIndexResolver.class);
private final MongoMappingContext mappingContext;
/**
@@ -85,6 +96,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root.getType()));
final CycleGuard guard = new CycleGuard();
root.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
@Override
@@ -92,7 +105,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
if (persistentProperty.isEntity()) {
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(),
persistentProperty.getFieldName(), root.getCollection()));
persistentProperty.getFieldName(), root.getCollection(), guard));
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(
@@ -115,7 +128,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* @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) {
private List<IndexDefinitionHolder> resolveIndexForClass(final Class<?> type, final String path,
final String collection, final CycleGuard guard) {
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, type));
@@ -127,9 +141,15 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + persistentProperty.getFieldName();
guard.protect(persistentProperty, path);
if (persistentProperty.isEntity()) {
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(), propertyDotPath, collection));
try {
indexInformation.addAll(resolveIndexForClass(persistentProperty.getActualType(), propertyDotPath,
collection, guard));
} catch (CyclicPropertyReferenceException e) {
LOGGER.warn(e.getMessage());
}
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(propertyDotPath,
@@ -158,7 +178,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private List<IndexDefinitionHolder> potentiallyCreateCompoundIndexDefinitions(String dotPath, String collection,
Class<?> type) {
if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null) {
if (AnnotationUtils.findAnnotation(type, CompoundIndexes.class) == null
&& AnnotationUtils.findAnnotation(type, CompoundIndex.class) == null) {
return Collections.emptyList();
}
@@ -176,38 +197,77 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
protected List<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String fallbackCollection,
Class<?> type) {
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
CompoundIndexes indexes = AnnotationUtils.findAnnotation(type, CompoundIndexes.class);
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>(
indexes.value().length);
for (CompoundIndex index : indexes.value()) {
if (indexes != null) {
for (CompoundIndex index : indexes.value()) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
}
}
String path = StringUtils.hasText(index.name()) ? index.name() : dotPath;
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
CompoundIndex index = AnnotationUtils.findAnnotation(type, CompoundIndex.class);
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition((DBObject) JSON.parse(index.def()));
if (!index.useGeneratedName()) {
indexDefinition.named(index.name());
}
if (index.unique()) {
indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN);
}
if (index.sparse()) {
indexDefinition.sparse();
}
if (index.background()) {
indexDefinition.background();
}
if (index.expireAfterSeconds() >= 0) {
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
}
indexDefinitions.add(new IndexDefinitionHolder(path, indexDefinition, collection));
if (index != null) {
indexDefinitions.add(createCompoundIndexDefinition(dotPath, fallbackCollection, index));
}
return indexDefinitions;
}
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String fallbackCollection,
CompoundIndex index) {
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(resolveCompoundIndexKeyFromStringDefinition(
dotPath, index.def()));
if (!index.useGeneratedName()) {
indexDefinition.named(index.name());
}
if (index.unique()) {
indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN);
}
if (index.sparse()) {
indexDefinition.sparse();
}
if (index.background()) {
indexDefinition.background();
}
if (index.expireAfterSeconds() >= 0) {
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
}
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
private DBObject resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString) {
if (!StringUtils.hasText(dotPath) && !StringUtils.hasText(keyDefinitionString)) {
throw new InvalidDataAccessApiUsageException("Cannot create index on root level for empty keys.");
}
if (!StringUtils.hasText(keyDefinitionString)) {
return new BasicDBObject(dotPath, 1);
}
DBObject dbo = (DBObject) JSON.parse(keyDefinitionString);
if (!StringUtils.hasText(dotPath)) {
return dbo;
}
BasicDBObjectBuilder dboBuilder = new BasicDBObjectBuilder();
for (String key : dbo.keySet()) {
dboBuilder.add(dotPath + "." + key, dbo.get(key));
}
return dboBuilder.get();
}
/**
* Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link Indexed} for given
* {@link MongoPersistentProperty}.
@@ -223,24 +283,25 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
Indexed index = persitentProperty.findAnnotation(Indexed.class);
String collection = StringUtils.hasText(index.collection()) ? index.collection() : fallbackCollection;
Index indexDefinition = new Index();
Index indexDefinition = new Index().on(dotPath,
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
if (!index.useGeneratedName()) {
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : persitentProperty.getFieldName());
indexDefinition.named(StringUtils.hasText(index.name()) ? index.name() : dotPath);
}
indexDefinition.on(persitentProperty.getFieldName(),
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
if (index.unique()) {
indexDefinition.unique(index.dropDups() ? Duplicates.DROP : Duplicates.RETAIN);
}
if (index.sparse()) {
indexDefinition.sparse();
}
if (index.background()) {
indexDefinition.background();
}
if (index.expireAfterSeconds() >= 0) {
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
}
@@ -276,6 +337,115 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
/**
* {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
* to detect potential cycles within the references.
*
* @author Christoph Strobl
*/
private static class CycleGuard {
private final Map<String, List<Path>> propertyTypeMap;
CycleGuard() {
this.propertyTypeMap = new LinkedHashMap<String, List<Path>>();
}
/**
* @param property The property to inspect
* @param path The path under which the property can be reached.
* @throws CyclicPropertyReferenceException in case a potential cycle is detected.
*/
void protect(MongoPersistentProperty property, String path) throws CyclicPropertyReferenceException {
String propertyTypeKey = createMapKey(property);
if (propertyTypeMap.containsKey(propertyTypeKey)) {
List<Path> paths = propertyTypeMap.get(propertyTypeKey);
for (Path existingPath : paths) {
if (existingPath.cycles(property)) {
paths.add(new Path(property, path));
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
existingPath.getPath());
}
}
paths.add(new Path(property, path));
} else {
ArrayList<Path> paths = new ArrayList<Path>();
paths.add(new Path(property, path));
propertyTypeMap.put(propertyTypeKey, paths);
}
}
private String createMapKey(MongoPersistentProperty property) {
return property.getOwner().getType().getSimpleName() + ":" + property.getFieldName();
}
private static class Path {
private final MongoPersistentProperty property;
private final String path;
Path(MongoPersistentProperty property, String path) {
this.property = property;
this.path = path;
}
public String getPath() {
return path;
}
boolean cycles(MongoPersistentProperty property) {
Pattern pattern = Pattern.compile("\\p{Punct}?" + Pattern.quote(property.getFieldName()) + "(\\p{Punct}|\\w)?");
Matcher matcher = pattern.matcher(path);
int count = 0;
while (matcher.find()) {
count++;
}
return count >= 1 && property.getOwner().getType().equals(this.property.getOwner().getType());
}
}
}
/**
* @author Christoph Strobl
* @since 1.5
*/
public static class CyclicPropertyReferenceException extends RuntimeException {
private static final long serialVersionUID = -3762979307658772277L;
private final String propertyName;
private final Class<?> type;
private final String dotPath;
public CyclicPropertyReferenceException(String propertyName, Class<?> type, String dotPath) {
this.propertyName = propertyName;
this.type = type;
this.dotPath = dotPath;
}
/*
* (non-Javadoc)
* @see java.lang.Throwable#getMessage()
*/
@Override
public String getMessage() {
return String.format("Found cycle for field '%s' in type '%s' for path '%s'", propertyName, type.getSimpleName(),
dotPath);
}
}
/**
* 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.
@@ -285,9 +455,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
*/
public static class IndexDefinitionHolder implements IndexDefinition {
private String path;
private IndexDefinition indexDefinition;
private String collection;
private final String path;
private final IndexDefinition indexDefinition;
private final String collection;
/**
* Create
@@ -306,7 +476,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
/**
* Get the {@liteal "dot"} path used to create the index.
* Get the {@literal "dot"} path used to create the index.
*
* @return
*/

View File

@@ -212,11 +212,11 @@ The base package in which to scan for entities annotated with @Document
<xsd:union memberTypes="xsd:boolean xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="abbreviate-field-names" use="optional" default="false">
<xsd:attribute name="abbreviate-field-names" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mongodb.core.mapping.CamelCaseAbbreviatingFieldNamingStrategy">
Enables abbreviating the field names for domain class properties to the
first character of their camel case names, e.g. fooBar -> fb.
first character of their camel case names, e.g. fooBar -> fb. Defaults to false.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@@ -226,7 +226,7 @@ The base package in which to scan for entities annotated with @Document
<xsd:attribute name="field-naming-strategy-ref" type="fieldNamingStrategyRef" use="optional">
<xsd:annotation>
<xsd:documentation source="org.springframework.data.mongodb.core.mapping.FieldNamingStrategy">
The reference to a MappingContext. Will default to 'mappingContext'.
The reference to a FieldNamingStrategy.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -654,4 +654,4 @@ The GridFs bucket string.]]></xsd:documentation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</xsd:schema>

View File

@@ -25,6 +25,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@@ -49,6 +50,7 @@ import com.mongodb.DBObject;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Ryan Tenney
*/
public class MappingMongoConverterParserIntegrationTests {
@@ -114,7 +116,7 @@ public class MappingMongoConverterParserIntegrationTests {
public void rejectsInvalidFieldNamingStrategyConfiguration() {
exception.expect(BeanDefinitionParsingException.class);
exception.expectMessage("abbreviate-field-names");
exception.expectMessage("abbreviation");
exception.expectMessage("field-naming-strategy-ref");
BeanDefinitionRegistry factory = new DefaultListableBeanFactory();
@@ -134,6 +136,22 @@ public class MappingMongoConverterParserIntegrationTests {
loadNestedBeanConfiguration();
}
/**
* @see DATAMONGO-925, DATAMONGO-928
*/
@Test
public void shouldSupportCustomFieldNamingStrategy() {
assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategy");
}
/**
* @see DATAMONGO-925, DATAMONGO-928
*/
@Test
public void shouldNotFailLoadingConfigIfAbbreviationIsDisabledAndStrategySet() {
assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategyAndAbbreviationDisabled");
}
private void loadValidConfiguration() {
this.loadConfiguration("namespace/converter.xml");
}
@@ -148,6 +166,19 @@ public class MappingMongoConverterParserIntegrationTests {
reader.loadBeanDefinitions(new ClassPathResource(configLocation));
}
private static void assertStrategyReferenceSetFor(String beanId) {
BeanDefinitionRegistry factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-custom-fieldnamingstrategy.xml"));
BeanDefinition definition = reader.getRegistry().getBeanDefinition(beanId.concat(".mongoMappingContext"));
BeanReference value = (BeanReference) definition.getPropertyValues().getPropertyValue("fieldNamingStrategy")
.getValue();
assertThat(value.getBeanName(), is("customFieldNamingStrategy"));
}
@Component
public static class SampleConverter implements Converter<Person, DBObject> {
public DBObject convert(Person source) {

View File

@@ -34,6 +34,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.Person;
@@ -49,6 +51,7 @@ import org.springframework.data.mongodb.core.query.Query;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
@@ -586,6 +589,17 @@ public class QueryMapperUnitTests {
assertThat(dbo.toString(), equalTo("{ \"embedded\" : { \"$in\" : [ { \"_id\" : \"1\"} , { \"_id\" : \"2\"}]}}"));
}
/**
* @see DATAMONGO-647
*/
@Test
public void customizedFieldNameShouldBeMappedCorrectlyWhenApplyingSort() {
Query query = query(where("field").is("bar")).with(new Sort(Direction.DESC, "field"));
DBObject dbo = mapper.getMappedObject(query.getSortObject(), context.getPersistentEntity(CustomizedField.class));
assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("foo", -1).get()));
}
@Document
public class Foo {
@Id private ObjectId id;

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.index;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.Date;
@@ -27,7 +28,6 @@ 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;
@@ -72,10 +72,10 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
optionsCaptor = ArgumentCaptor.forClass(DBObject.class);
collectionCaptor = ArgumentCaptor.forClass(String.class);
Mockito.when(factory.getDb()).thenReturn(db);
Mockito.when(db.getCollection(collectionCaptor.capture())).thenReturn(collection);
when(factory.getDb()).thenReturn(db);
when(db.getCollection(collectionCaptor.capture())).thenReturn(collection);
Mockito.doNothing().when(collection).createIndex(keysCaptor.capture(), optionsCaptor.capture());
doNothing().when(collection).createIndex(keysCaptor.capture(), optionsCaptor.capture());
}
@Test
@@ -106,7 +106,7 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
creator.onApplicationEvent(event);
Mockito.verifyZeroInteractions(collection);
verifyZeroInteractions(collection);
}
/**
@@ -181,6 +181,36 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
assertThat(optionsCaptor.getValue(), is(new BasicDBObjectBuilder().get()));
}
/**
* @see DATAMONGO-367
*/
@Test
public void indexCreationShouldNotCreateNewCollectionForNestedGeoSpatialIndexStructures() {
MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
ArgumentCaptor<String> collectionNameCapturer = ArgumentCaptor.forClass(String.class);
verify(db, times(1)).getCollection(collectionNameCapturer.capture());
assertThat(collectionNameCapturer.getValue(), equalTo("wrapper"));
}
/**
* @see DATAMONGO-367
*/
@Test
public void indexCreationShouldNotCreateNewCollectionForNestedIndexStructures() {
MongoMappingContext mappingContext = prepareMappingContext(IndexedDocumentWrapper.class);
new MongoPersistentEntityIndexCreator(mappingContext, factory);
ArgumentCaptor<String> collectionNameCapturer = ArgumentCaptor.forClass(String.class);
verify(db, times(1)).getCollection(collectionNameCapturer.capture());
assertThat(collectionNameCapturer.getValue(), equalTo("indexedDocumentWrapper"));
}
private static MongoMappingContext prepareMappingContext(Class<?> type) {
MongoMappingContext mappingContext = new MongoMappingContext();
@@ -233,6 +263,17 @@ public class MongoPersistentEntityIndexCreatorUnitTests {
@GeoSpatialIndexed Point location;
}
@Document
static class IndexedDocumentWrapper {
IndexedDocument indexedDocument;
}
static class IndexedDocument {
@Indexed String indexedValue;
}
@Document
class EntityWithGeneratedIndexName {

View File

@@ -277,7 +277,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelZero.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection("compound_index", "CompoundIndexOnLevelZero", indexDefinitions.get(0));
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
}
/**
@@ -324,7 +324,56 @@ public class MongoPersistentEntityIndexResolverUnitTests {
equalTo(new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).add("sparse", true)
.add("background", true).add("expireAfterSeconds", 10L).get()));
assertThat(indexDefinition.getIndexKeys(), equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get()));
}
/**
* @see DATAMONGO-929
*/
@Test
public void compoundIndexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelOne.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection(new String[] { "zero.foo", "zero.bar" }, "CompoundIndexOnLevelOne",
indexDefinitions.get(0));
}
/**
* @see DATAMONGO-929
*/
@Test
public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CompoundIndexOnLevelOneWithEmptyIndexDefinition.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection(new String[] { "zero" }, "CompoundIndexOnLevelZeroWithEmptyIndexDef",
indexDefinitions.get(0));
}
/**
* @see DATAMONGO-929
*/
@Test
public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(SingleCompoundIndex.class);
assertThat(indexDefinitions, hasSize(1));
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
}
@Document(collection = "CompoundIndexOnLevelOne")
static class CompoundIndexOnLevelOne {
CompoundIndexOnLevelZero zero;
}
@Document(collection = "CompoundIndexOnLevelZeroWithEmptyIndexDef")
static class CompoundIndexOnLevelOneWithEmptyIndexDefinition {
CompoundIndexOnLevelZeroWithEmptyIndexDef zero;
}
@Document(collection = "CompoundIndexOnLevelZero")
@@ -332,6 +381,15 @@ public class MongoPersistentEntityIndexResolverUnitTests {
dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true) })
static class CompoundIndexOnLevelZero {}
@CompoundIndexes({ @CompoundIndex(name = "compound_index", background = true, dropDups = true,
expireAfterSeconds = 10, sparse = true, unique = true) })
static class CompoundIndexOnLevelZeroWithEmptyIndexDef {}
@Document(collection = "CompoundIndexOnLevelZero")
@CompoundIndex(name = "compound_index", def = "{'foo': 1, 'bar': -1}", background = true, dropDups = true,
expireAfterSeconds = 10, sparse = true, unique = true)
static class SingleCompoundIndex {}
static class IndexDefinedOnSuperClass extends CompoundIndexOnLevelZero {
}
@@ -381,6 +439,55 @@ public class MongoPersistentEntityIndexResolverUnitTests {
assertThat(indexDefinitions, empty());
}
/**
* @see DATAMONGO-926
*/
@Test
public void shouldNotRunIntoStackOverflow() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleStartingInBetween.class);
assertThat(indexDefinitions, hasSize(1));
}
/**
* @see DATAMONGO-926
*/
@Test
public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleLevelZero.class);
assertIndexPathAndCollection("indexedProperty", "cycleLevelZero", indexDefinitions.get(0));
assertIndexPathAndCollection("cyclicReference.indexedProperty", "cycleLevelZero", indexDefinitions.get(1));
assertThat(indexDefinitions, hasSize(2));
}
/**
* @see DATAMONGO-926
*/
@Test
public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class);
assertIndexPathAndCollection("reference.indexedProperty", "cycleOnLevelOne", indexDefinitions.get(0));
assertIndexPathAndCollection("reference.cyclicReference.reference.indexedProperty", "cycleOnLevelOne",
indexDefinitions.get(1));
assertThat(indexDefinitions, hasSize(2));
}
/**
* @see DATAMONGO-926
*/
@Test
public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEqually() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(NoCycleButIdenticallyNamedProperties.class);
assertIndexPathAndCollection("foo", "noCycleButIdenticallyNamedProperties", indexDefinitions.get(0));
assertIndexPathAndCollection("reference.foo", "noCycleButIdenticallyNamedProperties", indexDefinitions.get(1));
assertIndexPathAndCollection("reference.deep.foo", "noCycleButIdenticallyNamedProperties",
indexDefinitions.get(2));
assertThat(indexDefinitions, hasSize(3));
}
@Document
static class MixedIndexRoot {
@@ -405,6 +512,48 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@Indexed Outer outer;
}
@Document
static class CycleLevelZero {
@Indexed String indexedProperty;
CycleLevelZero cyclicReference;
}
@Document
static class CycleOnLevelOne {
CycleOnLevelOneReferenced reference;
}
static class CycleOnLevelOneReferenced {
@Indexed String indexedProperty;
CycleOnLevelOne cyclicReference;
}
@Document
public static class CycleStartingInBetween {
CycleOnLevelOne referenceToCycleStart;
}
@Document
static class NoCycleButIdenticallyNamedProperties {
@Indexed String foo;
NoCycleButIdenticallyNamedPropertiesNested reference;
}
static class NoCycleButIdenticallyNamedPropertiesNested {
@Indexed String foo;
NoCycleButIndenticallNamedPropertiesDeeplyNested deep;
}
static class NoCycleButIndenticallNamedPropertiesDeeplyNested {
@Indexed String foo;
}
}
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {
@@ -426,7 +575,15 @@ public class MongoPersistentEntityIndexResolverUnitTests {
private static void assertIndexPathAndCollection(String expectedPath, String expectedCollection,
IndexDefinitionHolder holder) {
assertThat(holder.getPath(), equalTo(expectedPath));
assertIndexPathAndCollection(new String[] { expectedPath }, expectedCollection, holder);
}
private static void assertIndexPathAndCollection(String[] expectedPaths, String expectedCollection,
IndexDefinitionHolder holder) {
for (String expectedPath : expectedPaths) {
assertThat(holder.getIndexDefinition().getIndexKeys().containsField(expectedPath), equalTo(true));
}
assertThat(holder.getCollection(), equalTo(expectedCollection));
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<mongo:mapping-converter id="mappingConverterWithCustomFieldNamingStrategy" field-naming-strategy-ref="customFieldNamingStrategy" />
<!-- Should work as well as long ass abbreviation is explicitly disabled -->
<mongo:mapping-converter id="mappingConverterWithCustomFieldNamingStrategyAndAbbreviationDisabled" field-naming-strategy-ref="customFieldNamingStrategy" abbreviate-field-names="false" />
<bean id="customFieldNamingStrategy" class="org.mockito.Mockito" factory-method="mock">
<property name="type" value="org.springframework.data.mongodb.core.mapping.FieldNamingStrategy" />
</bean>
</beans>

View File

@@ -68,7 +68,7 @@
<xi:include href="introduction/introduction.xml"/>
<xi:include href="introduction/requirements.xml"/>
<xi:include href="introduction/getting-started.xml"/>
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RC1/src/docbkx/repositories.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RELEASE/src/docbkx/repositories.xml">
<xi:fallback href="../../../spring-data-commons/src/docbkx/repositories.xml" />
</xi:include>
</part>
@@ -88,10 +88,10 @@
<part id="appendix">
<title>Appendix</title>
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RC1/src/docbkx/repository-namespace-reference.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RELEASE/src/docbkx/repository-namespace-reference.xml">
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-namespace-reference.xml" />
</xi:include>
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RC1/src/docbkx/repository-query-keywords-reference.xml">
<xi:include href="https://raw.github.com/spring-projects/spring-data-commons/1.8.0.RELEASE/src/docbkx/repository-query-keywords-reference.xml">
<xi:fallback href="../../../spring-data-commons/src/docbkx/repository-query-keywords-reference.xml" />
</xi:include>
</part>

View File

@@ -1,6 +1,17 @@
Spring Data MongoDB Changelog
=============================
Changes in version 1.5.0.RELEASE (2014-05-20)
---------------------------------------------
* DATAMONGO-936 - Release 1.5 GA.
* DATAMONGO-929 - Index key should be the properties dot path when creating index using @Indexed / @CompoundIndex.
* DATAMONGO-928 - Error when using field-naming-strategy-ref.
* DATAMONGO-926 - Stack Overflow Error with 1.5.0.RC1 Release.
* DATAMONGO-925 - MappingMongoConverterParser is incorrectly rejecting field-naming-strategy-ref XML configuration.
* DATAMONGO-647 - Using "OrderBy" in "query by method name" ignores the @Field annotation for field alias.
* DATAMONGO-367 - @Indexed field in embedded Object creates new collection.
Changes in version 1.5.0.RC1 (2014-05-02)
-----------------------------------------
* DATAMONGO-924 - Aggregation not working with as() method in project() pipeline operator.

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 1.5 RC1
Spring Data MongoDB 1.5 GA
Copyright (c) [2010-2014] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").

View File

@@ -1,17 +0,0 @@
SPRING DATA MongoDB 1.4.0.RELEASE
---------------------------------
Spring Data MongoDB is released under the terms of the Apache Software License Version 2.0 (see license.txt).
DISTRIBUTION CONTENTS:
The JARs are available in the 'dist' directory, and the source JARs are in the 'src' directory.
The reference manual and javadoc are located in the 'docs' directory.
ADDITIONAL RESOURCES:
Spring Data Homepage: http://projects.spring.io/spring-data
Spring Data Forum: http://forum.spring.io/forum/spring-projects/data/nosql