From 21057c3d1791c68bf79e67084929d1bd51ce8e49 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 19 Jul 2022 11:17:40 +0200 Subject: [PATCH] Fix DTO projection instantiation. We now correctly instantiate DTO projection classes by using the actual constructor argument type. Previously, we did not update the conversion context to fetch the correct type but used the type of the DTO projection class instead of the constructor argument. Closes #4120 --- .../core/convert/MappingMongoConverter.java | 35 ++++++++++++------- .../MappingMongoConverterUnitTests.java | 33 +++++++++++++++++ 2 files changed, 56 insertions(+), 12 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 e0f5b53ea..af84e780c 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 @@ -17,7 +17,16 @@ package org.springframework.data.mongodb.core.convert; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -30,6 +39,7 @@ import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; import org.bson.json.JsonReader; import org.bson.types.ObjectId; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.context.ApplicationContext; @@ -395,8 +405,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App EntityProjection property = returnedTypeDescriptor.findProperty(name); if (property == null) { - return new ConversionContext(sourceConverter, conversions, path, MappingMongoConverter.this::readDocument, collectionConverter, - mapConverter, dbRefConverter, elementConverter); + return new ConversionContext(sourceConverter, conversions, path, MappingMongoConverter.this::readDocument, + collectionConverter, mapConverter, dbRefConverter, elementConverter); } return new ProjectingConversionContext(sourceConverter, conversions, path, collectionConverter, mapConverter, @@ -938,9 +948,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App TypeInformation type = prop.getTypeInformation(); if (conversions.hasValueConverter(prop)) { - accessor.put(prop, - conversions.getPropertyValueConversions().getValueConverter(prop) - .write(obj, new MongoConversionContext(prop, this))); + accessor.put(prop, conversions.getPropertyValueConversions().getValueConverter(prop).write(obj, + new MongoConversionContext(prop, this))); return; } @@ -1275,9 +1284,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App DocumentAccessor accessor = new DocumentAccessor(bson); if (conversions.hasValueConverter(property)) { - accessor.put(property, - conversions.getPropertyValueConversions().getValueConverter(property) - .write(value, new MongoConversionContext(property, this))); + accessor.put(property, conversions.getPropertyValueConversions().getValueConverter(property).write(value, + new MongoConversionContext(property, this))); return; } @@ -1927,12 +1935,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return null; } - if (context.conversions.hasValueConverter(property)) { - return (T) context.conversions.getPropertyValueConversions().getValueConverter(property).read(value, + CustomConversions conversions = context.conversions; + if (conversions.hasValueConverter(property)) { + return (T) conversions.getPropertyValueConversions().getValueConverter(property).read(value, new MongoConversionContext(property, context.sourceConverter)); } - return (T) context.convert(value, property.getTypeInformation()); + ConversionContext contextToUse = context.forProperty(property.getName()); + + return (T) contextToUse.convert(value, property.getTypeInformation()); } public MongoDbPropertyValueProvider withContext(ConversionContext context) { 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 e6b5c8db7..44f6d700d 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 @@ -45,6 +45,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.ConversionNotSupportedException; import org.springframework.beans.factory.annotation.Autowired; @@ -2707,6 +2708,23 @@ class MappingMongoConverterUnitTests { assertThat(projection.getName()).isEqualTo("my-book by Walter White"); } + @Test // GH-4120 + void shouldReadDtoProjection() { + + 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); + + AuthorOnly projection = converter.project(introspector.introspect(AuthorOnly.class, Book.class), book); + + assertThat(projection.getAuthor().getFirstName()).isEqualTo("Walter"); + assertThat(projection.getAuthor().getLastName()).isEqualTo("White"); + } + @Test // GH-3596 void simpleConverter() { @@ -3610,6 +3628,21 @@ class MappingMongoConverterUnitTests { String getName(); } + @lombok.Value + static class AuthorOnly { + + AuthorNameOnly author; + } + + @lombok.Value + static class AuthorNameOnly { + + String firstName; + + String lastName; + + } + @Data static class Book {