Browse Source

DATAMONGO-1210 - Fixed type hints for usage with findAndModify(…).

We now inspect the actual field type during update mapping and provide a type hint accordingly. Simple, non interface and non abstract types will no longer be decorated with the _class attribute. We now honor positional parameters when trying to map paths to properties. This allows more decent type mapping since we have now access to the meta model which allows us to check if presence of type hint (aka _class) is required.

We now add a special type hint indicating nested types to the converter. This allows more fine grained removal of _class property without the need to break the contract of MongoWriter.convertToMongoType(…).

Original pull request: #292.
pull/299/merge
Christoph Strobl 11 years ago committed by Oliver Gierke
parent
commit
394f695416
  1. 45
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
  2. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
  3. 73
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java
  4. 109
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
  5. 125
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java
  6. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java

45
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

@ -1019,6 +1019,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return removeTypeInfoRecursively(newDbo); return removeTypeInfoRecursively(newDbo);
} }
if (typeInformation.getType().equals(NestedDocument.class)) {
return removeTypeInfo(newDbo);
}
return !obj.getClass().equals(typeInformation.getType()) ? newDbo : removeTypeInfoRecursively(newDbo); return !obj.getClass().equals(typeInformation.getType()) ? newDbo : removeTypeInfoRecursively(newDbo);
} }
@ -1033,7 +1037,35 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
} }
/** /**
* Removes the type information from the conversion result. * Removes only the type information from the root document.
*
* @param object
* @return
*/
private Object removeTypeInfo(Object object) {
if (!(object instanceof DBObject)) {
return object;
}
DBObject dbObject = (DBObject) object;
String keyToRemove = null;
for (String key : dbObject.keySet()) {
if (typeMapper.isTypeKey(key)) {
keyToRemove = key;
}
}
if (keyToRemove != null) {
dbObject.removeField(keyToRemove);
}
return dbObject;
}
/**
* Removes the type information from the entire conversion result.
* *
* @param object * @param object
* @return * @return
@ -1194,4 +1226,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
DBObject readRef(DBRef ref) { DBObject readRef(DBRef ref) {
return dbRefResolver.fetch(ref); return dbRefResolver.fetch(ref);
} }
/**
* Marker class used to indicate we have a non root document object here that might be used within an update - so we
* need to preserve type hints for potential nested elements but need to remove it on top level.
*
* @author Christoph Strobl
* @since 1.8
*/
static class NestedDocument {
}
} }

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

@ -826,7 +826,7 @@ public class QueryMapper {
try { try {
PropertyPath path = PropertyPath.from(pathExpression, entity.getTypeInformation()); PropertyPath path = PropertyPath.from(pathExpression.replaceAll("\\.\\d", ""), entity.getTypeInformation());
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path); PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator(); Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();

73
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2014 the original author or authors. * Copyright 2013-2015 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.
@ -22,6 +22,7 @@ import java.util.Map.Entry;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.Association; import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
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.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
@ -29,6 +30,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update.Modifier; import org.springframework.data.mongodb.core.query.Update.Modifier;
import org.springframework.data.mongodb.core.query.Update.Modifiers; import org.springframework.data.mongodb.core.query.Update.Modifiers;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
@ -43,6 +45,7 @@ import com.mongodb.DBObject;
*/ */
public class UpdateMapper extends QueryMapper { public class UpdateMapper extends QueryMapper {
private static final ClassTypeInformation<?> NESTED_DOCUMENT = ClassTypeInformation.from(NestedDocument.class);
private final MongoConverter converter; private final MongoConverter converter;
/** /**
@ -66,7 +69,7 @@ public class UpdateMapper extends QueryMapper {
@Override @Override
protected Object delegateConvertToMongoType(Object source, MongoPersistentEntity<?> entity) { protected Object delegateConvertToMongoType(Object source, MongoPersistentEntity<?> entity) {
return entity == null ? super.delegateConvertToMongoType(source, null) : converter.convertToMongoType(source, return entity == null ? super.delegateConvertToMongoType(source, null) : converter.convertToMongoType(source,
entity.getTypeInformation()); getTypeHintForEntity(entity));
} }
/* /*
@ -97,14 +100,14 @@ public class UpdateMapper extends QueryMapper {
if (rawValue instanceof Modifier) { if (rawValue instanceof Modifier) {
value = getMappedValue((Modifier) rawValue); value = getMappedValue(field, (Modifier) rawValue);
} else if (rawValue instanceof Modifiers) { } else if (rawValue instanceof Modifiers) {
DBObject modificationOperations = new BasicDBObject(); DBObject modificationOperations = new BasicDBObject();
for (Modifier modifier : ((Modifiers) rawValue).getModifiers()) { for (Modifier modifier : ((Modifiers) rawValue).getModifiers()) {
modificationOperations.putAll(getMappedValue(modifier).toMap()); modificationOperations.putAll(getMappedValue(field, modifier).toMap());
} }
value = modificationOperations; value = modificationOperations;
@ -132,12 +135,40 @@ public class UpdateMapper extends QueryMapper {
return value instanceof Query; return value instanceof Query;
} }
private DBObject getMappedValue(Modifier modifier) { private DBObject getMappedValue(Field field, Modifier modifier) {
Object value = converter.convertToMongoType(modifier.getValue(), ClassTypeInformation.OBJECT); Object value = converter.convertToMongoType(modifier.getValue(), getTypeHintForField(field));
return new BasicDBObject(modifier.getKey(), value); return new BasicDBObject(modifier.getKey(), value);
} }
private TypeInformation<?> getTypeHintForField(Field field) {
if (field == null || field.getProperty() == null) {
return ClassTypeInformation.OBJECT;
}
if (field.getProperty().getActualType().isInterface()
|| java.lang.reflect.Modifier.isAbstract(field.getProperty().getActualType().getModifiers())) {
return ClassTypeInformation.OBJECT;
}
return NESTED_DOCUMENT;
}
private TypeInformation<?> getTypeHintForEntity(MongoPersistentEntity<?> entity) {
return processTypeHintForNestedDocuments(entity.getTypeInformation());
}
private TypeInformation<?> processTypeHintForNestedDocuments(TypeInformation<?> info) {
Class<?> type = info.getActualType().getType();
if (type.isInterface() || java.lang.reflect.Modifier.isAbstract(type.getModifiers())) {
return info;
}
return NESTED_DOCUMENT;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext) * @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext)
@ -233,7 +264,35 @@ public class UpdateMapper extends QueryMapper {
protected String mapPropertyName(MongoPersistentProperty property) { protected String mapPropertyName(MongoPersistentProperty property) {
String mappedName = PropertyToFieldNameConverter.INSTANCE.convert(property); String mappedName = PropertyToFieldNameConverter.INSTANCE.convert(property);
return iterator.hasNext() && iterator.next().equals("$") ? String.format("%s.$", mappedName) : mappedName;
boolean inspect = iterator.hasNext();
while (inspect) {
String partial = iterator.next();
boolean isPositional = isPositionalParameter(partial);
if (isPositional) {
mappedName += "." + partial;
}
inspect = isPositional && iterator.hasNext();
}
return mappedName;
}
boolean isPositionalParameter(String partial) {
if (partial.equals("$")) {
return true;
}
try {
Long.valueOf(partial);
return true;
} catch (NumberFormatException e) {
return false;
}
} }
} }

109
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java

@ -196,6 +196,7 @@ public class MongoTemplateTests {
template.dropCollection(SomeContent.class); template.dropCollection(SomeContent.class);
template.dropCollection(SomeTemplate.class); template.dropCollection(SomeTemplate.class);
template.dropCollection(Address.class); template.dropCollection(Address.class);
template.dropCollection(DocumentWithCollectionOfSamples.class);
} }
@Test @Test
@ -2210,11 +2211,12 @@ public class MongoTemplateTests {
assertThat(retrieved.model.value(), equalTo("value2")); assertThat(retrieved.model.value(), equalTo("value2"));
} }
// Rewrite the whole collection /**
// Passes in 1.6.0+, but changes order position of type hint _class * @see DATAMONGO-1210
*/
@Test @Test
public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollection() public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenWholeCollectionIsReplaced() {
{
DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); DocumentWithNestedCollection doc = new DocumentWithNestedCollection();
Map<String, Model> entry = new HashMap<String, Model>(); Map<String, Model> entry = new HashMap<String, Model>();
@ -2246,12 +2248,12 @@ public class MongoTemplateTests {
assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2"));
} }
/**
// Update first list element * @see DATAMONGO-1210
// Fails in 1.6.2+ */
@Test @Test
public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollection2() public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenFirstElementIsReplaced() {
{
DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); DocumentWithNestedCollection doc = new DocumentWithNestedCollection();
Map<String, Model> entry = new HashMap<String, Model>(); Map<String, Model> entry = new HashMap<String, Model>();
@ -2283,11 +2285,12 @@ public class MongoTemplateTests {
assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2"));
} }
// Add second list element /**
// Fails in 1.6.2+ * @see DATAMONGO-1210
*/
@Test @Test
public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollection2() public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollectionObjectInsertedAtSecondIndex() {
{
DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); DocumentWithNestedCollection doc = new DocumentWithNestedCollection();
Map<String, Model> entry = new HashMap<String, Model>(); Map<String, Model> entry = new HashMap<String, Model>();
@ -2318,15 +2321,18 @@ public class MongoTemplateTests {
assertThat(retrieved.models.get(1).get("key2").value(), equalTo("value2")); assertThat(retrieved.models.get(1).get("key2").value(), equalTo("value2"));
} }
// Update the collection of the embedded document /**
// Fails in 1.6.0+ * @see DATAMONGO-1210
*/
@Test @Test
public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollection() throws Exception public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingPositionedElement()
{ throws Exception {
List<Model> models = new ArrayList<Model>(); List<Model> models = new ArrayList<Model>();
models.add(new ModelA("value1")); models.add(new ModelA("value1"));
DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(new DocumentWithCollection(models)); DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(
new DocumentWithCollection(models));
template.save(doc); template.save(doc);
@ -2337,22 +2343,26 @@ public class MongoTemplateTests {
template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class); template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class);
DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class); DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query,
DocumentWithEmbeddedDocumentWithCollection.class);
assertThat(retrieved, notNullValue()); assertThat(retrieved, notNullValue());
assertThat(retrieved.embeddedDocument.models, hasSize(1)); assertThat(retrieved.embeddedDocument.models, hasSize(1));
assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2")); assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2"));
} }
// Update the collection of the embedded document /**
// Fails in 1.6.0+ * @see DATAMONGO-1210
*/
@Test @Test
public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollection2() throws Exception public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingSecondElement()
{ throws Exception {
List<Model> models = new ArrayList<Model>(); List<Model> models = new ArrayList<Model>();
models.add(new ModelA("value1")); models.add(new ModelA("value1"));
DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(new DocumentWithCollection(models)); DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(
new DocumentWithCollection(models));
template.save(doc); template.save(doc);
@ -2363,7 +2373,8 @@ public class MongoTemplateTests {
template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class); template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class);
DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class); DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query,
DocumentWithEmbeddedDocumentWithCollection.class);
assertThat(retrieved, notNullValue()); assertThat(retrieved, notNullValue());
assertThat(retrieved.embeddedDocument.models, hasSize(2)); assertThat(retrieved.embeddedDocument.models, hasSize(2));
@ -2371,35 +2382,42 @@ public class MongoTemplateTests {
assertThat(retrieved.embeddedDocument.models.get(1).value(), is("value2")); assertThat(retrieved.embeddedDocument.models.get(1).value(), is("value2"));
} }
// Rewrite the embedded document /**
// Fails in 1.6.0+ * @see DATAMONGO-1210
*/
@Test @Test
public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollection3() throws Exception public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenRewriting()
{ throws Exception {
List<Model> models = Arrays.<Model> asList(new ModelA("value1")); List<Model> models = Arrays.<Model> asList(new ModelA("value1"));
DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(new DocumentWithCollection(models)); DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(
new DocumentWithCollection(models));
template.save(doc); template.save(doc);
Query query = query(where("id").is(doc.id)); Query query = query(where("id").is(doc.id));
Update update = Update.update("embeddedDocument", new DocumentWithCollection(Arrays.<Model>asList(new ModelA("value2")))); Update update = Update.update("embeddedDocument",
new DocumentWithCollection(Arrays.<Model> asList(new ModelA("value2"))));
assertThat(template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class), notNullValue()); assertThat(template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class), notNullValue());
template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class); template.findAndModify(query, update, DocumentWithEmbeddedDocumentWithCollection.class);
DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class); DocumentWithEmbeddedDocumentWithCollection retrieved = template.findOne(query,
DocumentWithEmbeddedDocumentWithCollection.class);
assertThat(retrieved, notNullValue()); assertThat(retrieved, notNullValue());
assertThat(retrieved.embeddedDocument.models, hasSize(1)); assertThat(retrieved.embeddedDocument.models, hasSize(1));
assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2")); assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2"));
} }
// Fails in 1.6.0+ /**
* @see DATAMONGO-1210
*/
@Test @Test
public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWithNestedLists() public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWithNestedLists() {
{
DocumentWithNestedList doc = new DocumentWithNestedList(); DocumentWithNestedList doc = new DocumentWithNestedList();
List<Model> entry = new ArrayList<Model>(); List<Model> entry = new ArrayList<Model>();
@ -2835,8 +2853,12 @@ public class MongoTemplateTests {
assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3)); assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3));
} }
/**
* @see DATAMONGO-1210
*/
@Test @Test
public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSimpleDocuments() { public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSimpleDocuments() {
DocumentWithCollectionOfSamples doc = new DocumentWithCollectionOfSamples(); DocumentWithCollectionOfSamples doc = new DocumentWithCollectionOfSamples();
doc.samples = Arrays.asList(new Sample(null, "sample1")); doc.samples = Arrays.asList(new Sample(null, "sample1"));
@ -2846,7 +2868,7 @@ public class MongoTemplateTests {
assertThat(template.findOne(query, DocumentWithCollectionOfSamples.class), notNullValue()); assertThat(template.findOne(query, DocumentWithCollectionOfSamples.class), notNullValue());
Update update = new Update().addToSet("samples").each(Arrays.asList(new Sample(null, "sample2"), new Sample(null, "sample1"))); Update update = new Update().addToSet("samples").each(new Sample(null, "sample2"), new Sample(null, "sample1"));
template.findAndModify(query, update, DocumentWithCollectionOfSamples.class); template.findAndModify(query, update, DocumentWithCollectionOfSamples.class);
@ -3102,8 +3124,7 @@ public class MongoTemplateTests {
List<String> values; List<String> values;
} }
static class DocumentWithCollectionOfSamples static class DocumentWithCollectionOfSamples {
{
@Id String id; @Id String id;
List<Sample> samples; List<Sample> samples;
} }
@ -3114,25 +3135,21 @@ public class MongoTemplateTests {
List<String> string2; List<String> string2;
} }
static class DocumentWithNestedCollection static class DocumentWithNestedCollection {
{
@Id String id; @Id String id;
List<Map<String, Model>> models = new ArrayList<Map<String, Model>>(); List<Map<String, Model>> models = new ArrayList<Map<String, Model>>();
} }
static class DocumentWithNestedList static class DocumentWithNestedList {
{
@Id String id; @Id String id;
List<List<Model>> models = new ArrayList<List<Model>>(); List<List<Model>> models = new ArrayList<List<Model>>();
} }
static class DocumentWithEmbeddedDocumentWithCollection static class DocumentWithEmbeddedDocumentWithCollection {
{
@Id String id; @Id String id;
DocumentWithCollection embeddedDocument; DocumentWithCollection embeddedDocument;
DocumentWithEmbeddedDocumentWithCollection(DocumentWithCollection embeddedDocument) DocumentWithEmbeddedDocumentWithCollection(DocumentWithCollection embeddedDocument) {
{
this.embeddedDocument = embeddedDocument; this.embeddedDocument = embeddedDocument;
} }
} }

125
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java

@ -20,8 +20,10 @@ import static org.hamcrest.collection.IsMapContaining.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
@ -524,7 +526,6 @@ public class UpdateMapperUnitTests {
assertThat(((DBObject) updateValue).get("_class").toString(), assertThat(((DBObject) updateValue).get("_class").toString(),
equalTo("org.springframework.data.mongodb.core.convert.UpdateMapperUnitTests$ModelImpl")); equalTo("org.springframework.data.mongodb.core.convert.UpdateMapperUnitTests$ModelImpl"));
} }
} }
/** /**
@ -595,6 +596,109 @@ public class UpdateMapperUnitTests {
assertThat($unset, equalTo(new BasicDBObjectBuilder().add("dbRefAnnotatedList.$", 1).get())); assertThat($unset, equalTo(new BasicDBObjectBuilder().add("dbRefAnnotatedList.$", 1).get()));
} }
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingEachOperatorShouldNotAddTypeInfoForNonInterfaceNonAbstractTypes() {
Update update = new Update().addToSet("nestedDocs").each(new NestedDocument("nested-1"),
new NestedDocument("nested-2"));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithNestedCollection.class));
assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[0]._class"));
assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[1]._class"));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingEachOperatorShouldAddTypeHintForInterfaceTypes() {
Update update = new Update().addToSet("models").each(new ModelImpl(1), new ModelImpl(2));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ListModelWrapper.class));
assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[0]._class", ModelImpl.class.getName()));
assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[1]._class", ModelImpl.class.getName()));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingEachOperatorShouldAddTypeHintForAbstractTypes() {
Update update = new Update().addToSet("list").each(new ConcreteChildClass("foo", "one"),
new ConcreteChildClass("bar", "two"));
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
assertThat(mappedUpdate,
isBsonObject().containing("$addToSet.aliased.$each.[0]._class", ConcreteChildClass.class.getName()));
assertThat(mappedUpdate,
isBsonObject().containing("$addToSet.aliased.$each.[1]._class", ConcreteChildClass.class.getName()));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingShouldOnlyRemoveTypeHintFromTopLevelTypeInCaseOfNestedDocument() {
WrapperAroundInterfaceType wait = new WrapperAroundInterfaceType();
wait.interfaceType = new ModelImpl(1);
Update update = new Update().addToSet("listHoldingConcretyTypeWithInterfaceTypeAttribute").each(wait);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DomainTypeWithListOfConcreteTypesHavingSingleInterfaceTypeAttribute.class));
assertThat(mappedUpdate,
isBsonObject().notContaining("$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0]._class"));
assertThat(
mappedUpdate,
isBsonObject().containing(
"$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0].interfaceType._class",
ModelImpl.class.getName()));
}
/**
* @see DATAMONGO-1210
*/
@Test
public void mappingShouldRetainTypeInformationOfNestedListWhenUpdatingConcreteyParentType() {
ListModelWrapper lmw = new ListModelWrapper();
lmw.models = Collections.<Model> singletonList(new ModelImpl(1));
Update update = new Update().set("concreteTypeWithListAttributeOfInterfaceType", lmw);
DBObject mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes.class));
assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteTypeWithListAttributeOfInterfaceType._class"));
assertThat(
mappedUpdate,
isBsonObject().containing("$set.concreteTypeWithListAttributeOfInterfaceType.models.[0]._class",
ModelImpl.class.getName()));
}
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
ListModelWrapper concreteTypeWithListAttributeOfInterfaceType;
}
static class DomainTypeWithListOfConcreteTypesHavingSingleInterfaceTypeAttribute {
List<WrapperAroundInterfaceType> listHoldingConcretyTypeWithInterfaceTypeAttribute;
}
static class WrapperAroundInterfaceType {
Model interfaceType;
}
@org.springframework.data.mongodb.core.mapping.Document(collection = "DocumentWithReferenceToInterface") @org.springframework.data.mongodb.core.mapping.Document(collection = "DocumentWithReferenceToInterface")
static interface DocumentWithReferenceToInterface { static interface DocumentWithReferenceToInterface {
@ -728,6 +832,10 @@ public class UpdateMapperUnitTests {
static class DomainEntity { static class DomainEntity {
List<NestedEntity> collectionOfNestedEntities; List<NestedEntity> collectionOfNestedEntities;
public List<NestedEntity> getCollectionOfNestedEntities() {
return collectionOfNestedEntities;
}
} }
static class NestedEntity { static class NestedEntity {
@ -770,4 +878,19 @@ public class UpdateMapperUnitTests {
@Field("mapped") DocumentWithDBRefCollection nested; @Field("mapped") DocumentWithDBRefCollection nested;
} }
static class DocumentWithNestedCollection {
List<NestedDocument> nestedDocs;
}
static class NestedDocument {
String name;
public NestedDocument(String name) {
super();
this.name = name;
}
}
} }

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java

@ -90,6 +90,10 @@ public class IsBsonObject<T extends BSONObject> extends TypeSafeMatcher<T> {
return false; return false;
} }
if (o != null && expectation.not) {
return false;
}
} }
return true; return true;
} }

Loading…
Cancel
Save