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 c58f8d1e4..283bee2da 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 @@ -49,7 +49,16 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.annotation.Reference; import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.TypeMapper; -import org.springframework.data.mapping.*; +import org.springframework.data.mapping.AccessOptions; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.Parameter; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.mapping.PersistentPropertyPathAccessor; +import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; @@ -407,7 +416,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App EntityProjection property = returnedTypeDescriptor.findProperty(name); if (property == null) { - return super.forProperty(name); + return new ConversionContext(conversions, path, MappingMongoConverter.this::readDocument, collectionConverter, + mapConverter, dbRefConverter, elementConverter); } return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter, @@ -1954,12 +1964,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } public MongoDbPropertyValueProvider withContext(ConversionContext context) { - if (context == this.context) { - return this; - } - - return new MongoDbPropertyValueProvider(context, accessor, evaluator); + return context == this.context ? this : new MongoDbPropertyValueProvider(context, accessor, evaluator); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index 40ab7aa11..5e9b0c8ad 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.core.DocumentTestUtils.*; +import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -2689,8 +2690,8 @@ class MappingMongoConverterUnitTests { .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), mappingContext); - EntityProjection projection = introspector - .introspect(WithNestedProjection.class, Person.class); + EntityProjection projection = introspector.introspect(WithNestedProjection.class, + Person.class); WithNestedProjection person = converter.project(projection, source); assertThat(person.getAddresses()).extracting(AddressProjection::getStreet).hasSize(1).containsOnly("hwy"); @@ -2714,6 +2715,22 @@ class MappingMongoConverterUnitTests { assertThat(person.getAddresses()).extracting(Address::getStreet).hasSize(1).containsOnly("hwy"); } + @Test // GH-3998 + void shouldReadOpenProjection() { + + org.bson.Document author = new org.bson.Document("firstName", "Walter").append("lastName", "White"); + org.bson.Document book = new org.bson.Document("_id", "foo").append("name", "my-book").append("author", author); + + EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(), + EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy() + .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), + mappingContext); + + BookProjection projection = converter.project(introspector.introspect(BookProjection.class, Book.class), book); + + assertThat(projection.getName()).isEqualTo("my-book by Walter White"); + } + static class GenericType { T content; } @@ -3438,11 +3455,56 @@ class MappingMongoConverterUnitTests { @org.springframework.data.mongodb.core.mapping.Field( write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Integer writeAlways; - @org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field( + @org.springframework.data.mongodb.core.mapping.DBRef + @org.springframework.data.mongodb.core.mapping.Field( write = org.springframework.data.mongodb.core.mapping.Field.Write.NON_NULL) Person writeNonNullPerson; - @org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field( + @org.springframework.data.mongodb.core.mapping.DBRef + @org.springframework.data.mongodb.core.mapping.Field( write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Person writeAlwaysPerson; } + + interface BookProjection { + + @Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}") + String getName(); + } + + @Data + static class Book { + + @Id String id; + + String name; + + Author author = new Author(); + + public Book() {} + + public Book(String id, String name, Author author) { + this.id = id; + this.name = name; + this.author = author; + } + } + + static class Author { + + @Id String id; + + String firstName; + + String lastName; + + public Author() {} + + public Author(String id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + } + }