From eae5c600ba74144ed23fea31862fd4d6575e0d74 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 11 Oct 2023 10:20:15 +0200 Subject: [PATCH] Fix id value conversion when projecting result types. Contextual information required for converting values are now passed on correctly when projecting id properties. Closes: #4524 Original pull request: #4525 --- .../core/convert/MappingMongoConverter.java | 2 +- .../MappingMongoConverterUnitTests.java | 135 +++++++++++++++++- 2 files changed, 132 insertions(+), 5 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 cfeae40ae5..48fd2ce54d 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 @@ -569,7 +569,7 @@ private Object readIdValue(ConversionContext context, SpELExpressionEvaluator ev String expression = idProperty.getSpelExpression(); Object resolvedValue = expression != null ? evaluator.evaluate(expression) : rawId; - return resolvedValue != null ? readValue(context, resolvedValue, idProperty.getTypeInformation()) : null; + return resolvedValue != null ? readValue(context.forProperty(idProperty), resolvedValue, idProperty.getTypeInformation()) : null; } private void readProperties(ConversionContext context, MongoPersistentEntity entity, 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 ea00542018..efe96586f5 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 @@ -42,6 +42,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -83,6 +85,7 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.FieldType; +import org.springframework.data.mongodb.core.mapping.MongoId; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.PersonPojoStringId; @@ -2866,7 +2869,7 @@ public org.bson.Document write(@Nullable String domainValue, MongoConversionCont @Test // GH-4371 void shouldConvertTypesToStringTargetType() { - + org.bson.Document source = org.bson.Document.parse(""" { city : ["Gotham", "Metropolis"] @@ -2876,6 +2879,35 @@ void shouldConvertTypesToStringTargetType() { assertThat(converter.read(Address.class, source).city).isEqualTo("Gotham,Metropolis"); } + @ValueSource(classes = { ComplexIdAndNoAnnotation.class, ComplexIdAndIdAnnotation.class, + ComplexIdAndMongoIdAnnotation.class, ComplexIdAndFieldAnnotation.class }) + @ParameterizedTest // GH-4524 + void projectShouldReadComplexIdType(Class projectionTargetType) { + + EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(), + EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy() + .and((target, underlyingType) -> !converter.conversions.isSimpleType(target)), + mappingContext); + + ComplexId idValue = ComplexId.of(101L); + org.bson.Document source = new org.bson.Document("_id", new org.bson.Document("innerId", idValue.innerId)) + .append("value", "abc").append("_class", ComplexIdAndNoAnnotation.class.getName()); + + EntityProjection projection = introspector.introspect(projectionTargetType, + ComplexIdAndNoAnnotation.class); + + assertThat(converter.project(projection, source)) // + .isInstanceOf(projectionTargetType) // + .extracting("id").isEqualTo(idValue); + } + + org.bson.Document write(Object source) { + + org.bson.Document target = new org.bson.Document(); + converter.write(source, target); + return target; + } + static class GenericType { T content; } @@ -3092,7 +3124,33 @@ static class ClassWithComplexId { } static class ComplexId { + Long innerId; + + static ComplexId of(Long value) { + + ComplexId id = new ComplexId(); + id.innerId = value; + return id; + } + + @Override + public boolean equals(Object o) { + + if (o == this) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ComplexId complexId = (ComplexId) o; + return Objects.equals(innerId, complexId.innerId); + } + + @Override + public int hashCode() { + return Objects.hash(innerId); + } } static class TypWithCollectionConstructor { @@ -3611,10 +3669,12 @@ static class WithFieldWrite { @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; } @@ -3728,12 +3788,79 @@ static class Author { } - @Data static class Cyclic { @Id String id; String value; Cyclic cycle; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Cyclic getCycle() { + return cycle; + } + + public void setCycle(Cyclic cycle) { + this.cycle = cycle; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Cyclic cyclic = (Cyclic) o; + return Objects.equals(id, cyclic.id) && Objects.equals(value, cyclic.value) + && Objects.equals(cycle, cyclic.cycle); + } + + @Override + public int hashCode() { + return Objects.hash(id, value, cycle); + } + } + + static class ComplexIdAndFieldAnnotation { + + @Field("_id") // + ComplexId id; + String value; + } + + static class ComplexIdAndMongoIdAnnotation { + + @MongoId // + ComplexId id; + String value; } + static class ComplexIdAndIdAnnotation { + + @Id // + ComplexId id; + String value; + } + + static class ComplexIdAndNoAnnotation { + + ComplexId id; + String value; + } }