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 dc43ee994..68e2c517e 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 @@ -363,13 +363,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App populateProperties(context, mappedEntity, documentAccessor, evaluator, instance); - PersistentPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor<>(accessor, conversionService); - MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(context, documentAccessor, evaluator, - spELContext); - - readProperties(context, mappedEntity, convertingAccessor, documentAccessor, valueProvider, evaluator, - Predicates.isTrue()); - return accessor.getBean(); } @@ -506,16 +499,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); S instance = instantiator.createInstance(entity, provider); - if (entity.requiresPropertyPopulation()) { - return populateProperties(context, entity, documentAccessor, evaluator, instance); - } - - return instance; + return populateProperties(context, entity, documentAccessor, evaluator, instance); } private S populateProperties(ConversionContext context, MongoPersistentEntity entity, DocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) { + if (!entity.requiresPropertyPopulation()) { + return instance; + } + PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance), conversionService); @@ -566,7 +559,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App String expression = idProperty.getSpelExpression(); Object resolvedValue = expression != null ? evaluator.evaluate(expression) : rawId; - return resolvedValue != null ? readValue(context.forProperty(idProperty), resolvedValue, idProperty.getTypeInformation()) : null; + return resolvedValue != null + ? readValue(context.forProperty(idProperty), resolvedValue, idProperty.getTypeInformation()) + : null; } private void readProperties(ConversionContext context, MongoPersistentEntity entity, @@ -622,9 +617,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } @Nullable - private Object readAssociation(Association association, - DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback, - ConversionContext context) { + private Object readAssociation(Association association, DocumentAccessor documentAccessor, + DbRefProxyHandler handler, DbRefResolverCallback callback, ConversionContext context) { MongoPersistentProperty property = association.getInverse(); Object value = documentAccessor.get(property); @@ -647,7 +641,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } else { return dbRefResolver.resolveReference(property, - new DocumentReferenceSource(documentAccessor.getDocument(), documentAccessor.get(property)), + new DocumentReferenceSource(documentAccessor.getDocument(), documentAccessor.get(property)), referenceLookupDelegate, context.forProperty(property)::convert); } } @@ -2426,8 +2420,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App this.returnedTypeDescriptor = projection; } - - @Override public ConversionContext forProperty(String name) { 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 fb22b6c9b..1710e1386 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 @@ -28,11 +28,14 @@ import lombok.RequiredArgsConstructor; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; +import java.nio.ByteBuffer; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.*; +import org.assertj.core.data.Percentage; +import org.bson.BsonUndefined; import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.Decimal128; @@ -125,7 +128,8 @@ class MappingMongoConverterUnitTests { @BeforeEach void beforeEach() { - MongoCustomConversions conversions = new MongoCustomConversions(); + MongoCustomConversions conversions = new MongoCustomConversions( + Arrays.asList(new ByteBufferToDoubleHolderConverter())); mappingContext = new MongoMappingContext(); mappingContext.setApplicationContext(context); @@ -1437,7 +1441,7 @@ class MappingMongoConverterUnitTests { assertThat(document.get("circle")).isInstanceOf(org.bson.Document.class); assertThat(document.get("circle")).isEqualTo((Object) new org.bson.Document("center", new org.bson.Document("x", circle.getCenter().getX()).append("y", circle.getCenter().getY())) - .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); + .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); } @Test // DATAMONGO-858 @@ -1470,7 +1474,7 @@ class MappingMongoConverterUnitTests { assertThat(document.get("sphere")).isInstanceOf(org.bson.Document.class); assertThat(document.get("sphere")).isEqualTo((Object) new org.bson.Document("center", new org.bson.Document("x", sphere.getCenter().getX()).append("y", sphere.getCenter().getY())) - .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); + .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); } @Test // DATAMONGO-858 @@ -1488,7 +1492,7 @@ class MappingMongoConverterUnitTests { assertThat(document.get("sphere")).isInstanceOf(org.bson.Document.class); assertThat(document.get("sphere")).isEqualTo((Object) new org.bson.Document("center", new org.bson.Document("x", sphere.getCenter().getX()).append("y", sphere.getCenter().getY())) - .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); + .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); } @Test // DATAMONGO-858 @@ -1521,7 +1525,7 @@ class MappingMongoConverterUnitTests { assertThat(document.get("shape")).isInstanceOf(org.bson.Document.class); assertThat(document.get("shape")).isEqualTo((Object) new org.bson.Document("center", new org.bson.Document("x", sphere.getCenter().getX()).append("y", sphere.getCenter().getY())) - .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); + .append("radius", radius.getNormalizedValue()).append("metric", radius.getMetric().toString())); } @Test // DATAMONGO-858 @@ -2531,8 +2535,8 @@ class MappingMongoConverterUnitTests { converter.afterPropertiesSet(); org.bson.Document source = new org.bson.Document("typeImplementingMap", - new org.bson.Document("1st", "one").append("2nd", 2)).append("_class", - TypeWrappingTypeImplementingMap.class.getName()); + new org.bson.Document("1st", "one").append("2nd", 2)) + .append("_class", TypeWrappingTypeImplementingMap.class.getName()); TypeWrappingTypeImplementingMap target = converter.read(TypeWrappingTypeImplementingMap.class, source); @@ -2720,8 +2724,8 @@ class MappingMongoConverterUnitTests { .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), mappingContext); - EntityProjection projection = introspector.introspect(WithNestedInterfaceProjection.class, - Person.class); + EntityProjection projection = introspector + .introspect(WithNestedInterfaceProjection.class, Person.class); WithNestedInterfaceProjection person = converter.project(projection, source); assertThat(person.getFirstname()).isEqualTo("spring"); @@ -2739,14 +2743,35 @@ class MappingMongoConverterUnitTests { .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), mappingContext); - EntityProjection projection = introspector.introspect(WithNestedDtoProjection.class, - Person.class); + EntityProjection projection = introspector + .introspect(WithNestedDtoProjection.class, Person.class); WithNestedDtoProjection person = converter.project(projection, source); assertThat(person.getFirstname()).isEqualTo("spring"); assertThat(person.getAddress().getStreet()).isEqualTo("data"); } + @Test // GH-4626 + void projectShouldReadDtoProjectionPropertiesOnlyOnce() { + + ByteBuffer number = ByteBuffer.allocate(8); + number.putDouble(1.2d); + number.flip(); + + org.bson.Document source = new org.bson.Document("number", number); + + EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(), + EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy() + .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), + mappingContext); + + EntityProjection projection = introspector.introspect(DoubleHolderDto.class, + WithDoubleHolder.class); + DoubleHolderDto result = converter.project(projection, source); + + assertThat(result.number.number).isCloseTo(1.2, Percentage.withPercentage(1)); + } + @Test // GH-2860 void projectShouldReadProjectionWithNestedEntity() { @@ -3022,11 +3047,13 @@ class MappingMongoConverterUnitTests { interface WithNestedInterfaceProjection { String getFirstname(); + AddressProjection getAddress(); } interface WithNestedDtoProjection { String getFirstname(); + AddressDto getAddress(); } @@ -3925,4 +3952,30 @@ class MappingMongoConverterUnitTests { ComplexId id; String value; } + + @ReadingConverter + static class ByteBufferToDoubleHolderConverter implements Converter { + + @Override + public DoubleHolder convert(ByteBuffer source) { + return new DoubleHolder(source.getDouble()); + } + } + + record DoubleHolder(double number) { + + } + + static class WithDoubleHolder { + DoubleHolder number; + } + + static class DoubleHolderDto { + DoubleHolder number; + + public DoubleHolderDto(DoubleHolder number) { + this.number = number; + } + } + }