From 394f695416ce76e98f52a186dcb0de7f25635ff5 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 29 Apr 2015 08:02:30 +0200 Subject: [PATCH] =?UTF-8?q?DATAMONGO-1210=20-=20Fixed=20type=20hints=20for?= =?UTF-8?q?=20usage=20with=20findAndModify(=E2=80=A6).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../core/convert/MappingMongoConverter.java | 45 ++++++- .../mongodb/core/convert/QueryMapper.java | 2 +- .../mongodb/core/convert/UpdateMapper.java | 73 +++++++++- .../data/mongodb/core/MongoTemplateTests.java | 113 +++++++++------- .../core/convert/UpdateMapperUnitTests.java | 125 +++++++++++++++++- .../data/mongodb/test/util/IsBsonObject.java | 4 + 6 files changed, 304 insertions(+), 58 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 1776255ff..ea0d35678 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/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); } + if (typeInformation.getType().equals(NestedDocument.class)) { + return removeTypeInfo(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 * @return @@ -1194,4 +1226,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App DBObject readRef(DBRef 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 { + + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index ef9d8a6cf..b6f1292c0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -826,7 +826,7 @@ public class QueryMapper { try { - PropertyPath path = PropertyPath.from(pathExpression, entity.getTypeInformation()); + PropertyPath path = PropertyPath.from(pathExpression.replaceAll("\\.\\d", ""), entity.getTypeInformation()); PersistentPropertyPath propertyPath = mappingContext.getPersistentPropertyPath(path); Iterator iterator = propertyPath.iterator(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java index c9314b965..d3e89c6ba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java +++ b/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"); * 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.data.mapping.Association; 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.MongoPersistentProperty; 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.Modifiers; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; import com.mongodb.BasicDBObject; @@ -43,6 +45,7 @@ import com.mongodb.DBObject; */ public class UpdateMapper extends QueryMapper { + private static final ClassTypeInformation NESTED_DOCUMENT = ClassTypeInformation.from(NestedDocument.class); private final MongoConverter converter; /** @@ -66,7 +69,7 @@ public class UpdateMapper extends QueryMapper { @Override protected Object delegateConvertToMongoType(Object source, MongoPersistentEntity entity) { 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) { - value = getMappedValue((Modifier) rawValue); + value = getMappedValue(field, (Modifier) rawValue); } else if (rawValue instanceof Modifiers) { DBObject modificationOperations = new BasicDBObject(); for (Modifier modifier : ((Modifiers) rawValue).getModifiers()) { - modificationOperations.putAll(getMappedValue(modifier).toMap()); + modificationOperations.putAll(getMappedValue(field, modifier).toMap()); } value = modificationOperations; @@ -132,12 +135,40 @@ public class UpdateMapper extends QueryMapper { 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); } + 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) * @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) { 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; + } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index da9ae7aa2..914d4bd4c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/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(SomeTemplate.class); template.dropCollection(Address.class); + template.dropCollection(DocumentWithCollectionOfSamples.class); } @Test @@ -2210,11 +2211,12 @@ public class MongoTemplateTests { 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 - public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollection() - { + public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenWholeCollectionIsReplaced() { + DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); Map entry = new HashMap(); @@ -2246,12 +2248,12 @@ public class MongoTemplateTests { assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); } - - // Update first list element - // Fails in 1.6.2+ + /** + * @see DATAMONGO-1210 + */ @Test - public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollection2() - { + public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenFirstElementIsReplaced() { + DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); Map entry = new HashMap(); @@ -2283,11 +2285,12 @@ public class MongoTemplateTests { assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); } - // Add second list element - // Fails in 1.6.2+ + /** + * @see DATAMONGO-1210 + */ @Test - public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollection2() - { + public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollectionObjectInsertedAtSecondIndex() { + DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); Map entry = new HashMap(); @@ -2318,15 +2321,18 @@ public class MongoTemplateTests { 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 - public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollection() throws Exception - { + public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingPositionedElement() + throws Exception { + List models = new ArrayList(); models.add(new ModelA("value1")); - DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(new DocumentWithCollection(models)); + DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection( + new DocumentWithCollection(models)); template.save(doc); @@ -2337,22 +2343,26 @@ public class MongoTemplateTests { 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.embeddedDocument.models, hasSize(1)); 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 - public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollection2() throws Exception - { + public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingSecondElement() + throws Exception { + List models = new ArrayList(); models.add(new ModelA("value1")); - DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(new DocumentWithCollection(models)); + DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection( + new DocumentWithCollection(models)); template.save(doc); @@ -2363,7 +2373,8 @@ public class MongoTemplateTests { 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.embeddedDocument.models, hasSize(2)); @@ -2371,35 +2382,42 @@ public class MongoTemplateTests { assertThat(retrieved.embeddedDocument.models.get(1).value(), is("value2")); } - // Rewrite the embedded document - // Fails in 1.6.0+ + /** + * @see DATAMONGO-1210 + */ @Test - public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollection3() throws Exception - { - List models = Arrays.asList(new ModelA("value1")); + public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenRewriting() + throws Exception { + + List models = Arrays. asList(new ModelA("value1")); - DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection(new DocumentWithCollection(models)); + DocumentWithEmbeddedDocumentWithCollection doc = new DocumentWithEmbeddedDocumentWithCollection( + new DocumentWithCollection(models)); template.save(doc); Query query = query(where("id").is(doc.id)); - Update update = Update.update("embeddedDocument", new DocumentWithCollection(Arrays.asList(new ModelA("value2")))); + Update update = Update.update("embeddedDocument", + new DocumentWithCollection(Arrays. asList(new ModelA("value2")))); assertThat(template.findOne(query, DocumentWithEmbeddedDocumentWithCollection.class), notNullValue()); 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.embeddedDocument.models, hasSize(1)); assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2")); } - // Fails in 1.6.0+ + /** + * @see DATAMONGO-1210 + */ @Test - public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWithNestedLists() - { + public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWithNestedLists() { + DocumentWithNestedList doc = new DocumentWithNestedList(); List entry = new ArrayList(); @@ -2835,8 +2853,12 @@ public class MongoTemplateTests { assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3)); } + /** + * @see DATAMONGO-1210 + */ @Test public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSimpleDocuments() { + DocumentWithCollectionOfSamples doc = new DocumentWithCollectionOfSamples(); doc.samples = Arrays.asList(new Sample(null, "sample1")); @@ -2846,7 +2868,7 @@ public class MongoTemplateTests { 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); @@ -3102,8 +3124,7 @@ public class MongoTemplateTests { List values; } - static class DocumentWithCollectionOfSamples - { + static class DocumentWithCollectionOfSamples { @Id String id; List samples; } @@ -3114,25 +3135,21 @@ public class MongoTemplateTests { List string2; } - static class DocumentWithNestedCollection - { + static class DocumentWithNestedCollection { @Id String id; List> models = new ArrayList>(); } - static class DocumentWithNestedList - { + static class DocumentWithNestedList { @Id String id; List> models = new ArrayList>(); - } + } - static class DocumentWithEmbeddedDocumentWithCollection - { + static class DocumentWithEmbeddedDocumentWithCollection { @Id String id; DocumentWithCollection embeddedDocument; - DocumentWithEmbeddedDocumentWithCollection(DocumentWithCollection embeddedDocument) - { + DocumentWithEmbeddedDocumentWithCollection(DocumentWithCollection embeddedDocument) { this.embeddedDocument = embeddedDocument; } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index d2cc763bb..b88b773fc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/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.mockito.Mockito.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; +import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.hamcrest.Matcher; @@ -524,7 +526,6 @@ public class UpdateMapperUnitTests { assertThat(((DBObject) updateValue).get("_class").toString(), 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())); } + /** + * @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. 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 listHoldingConcretyTypeWithInterfaceTypeAttribute; + } + + static class WrapperAroundInterfaceType { + Model interfaceType; + } + @org.springframework.data.mongodb.core.mapping.Document(collection = "DocumentWithReferenceToInterface") static interface DocumentWithReferenceToInterface { @@ -728,6 +832,10 @@ public class UpdateMapperUnitTests { static class DomainEntity { List collectionOfNestedEntities; + + public List getCollectionOfNestedEntities() { + return collectionOfNestedEntities; + } } static class NestedEntity { @@ -770,4 +878,19 @@ public class UpdateMapperUnitTests { @Field("mapped") DocumentWithDBRefCollection nested; } + + static class DocumentWithNestedCollection { + List nestedDocs; + } + + static class NestedDocument { + String name; + + public NestedDocument(String name) { + super(); + this.name = name; + } + + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java index 04f4b4ce9..0657dd131 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/IsBsonObject.java @@ -90,6 +90,10 @@ public class IsBsonObject extends TypeSafeMatcher { return false; } + if (o != null && expectation.not) { + return false; + } + } return true; }