Skip to content

Commit 7ce2143

Browse files
committed
Create a new conversion context for projection properties.
We now create a new conversion context to ensure that we use the correct property type to avoid type retention when mapping complex objects within a projection. Closes #3998
1 parent 451d996 commit 7ce2143

File tree

2 files changed

+79
-11
lines changed

2 files changed

+79
-11
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,16 @@
4949
import org.springframework.data.annotation.Reference;
5050
import org.springframework.data.convert.CustomConversions;
5151
import org.springframework.data.convert.TypeMapper;
52-
import org.springframework.data.mapping.*;
52+
import org.springframework.data.mapping.AccessOptions;
53+
import org.springframework.data.mapping.Association;
54+
import org.springframework.data.mapping.MappingException;
55+
import org.springframework.data.mapping.Parameter;
56+
import org.springframework.data.mapping.PersistentEntity;
57+
import org.springframework.data.mapping.PersistentProperty;
58+
import org.springframework.data.mapping.PersistentPropertyAccessor;
59+
import org.springframework.data.mapping.PersistentPropertyPath;
60+
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
61+
import org.springframework.data.mapping.PreferredConstructor;
5362
import org.springframework.data.mapping.callback.EntityCallbacks;
5463
import org.springframework.data.mapping.context.MappingContext;
5564
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@@ -407,7 +416,8 @@ public ConversionContext forProperty(String name) {
407416

408417
EntityProjection<?, ?> property = returnedTypeDescriptor.findProperty(name);
409418
if (property == null) {
410-
return super.forProperty(name);
419+
return new ConversionContext(conversions, path, MappingMongoConverter.this::readDocument, collectionConverter,
420+
mapConverter, dbRefConverter, elementConverter);
411421
}
412422

413423
return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter,
@@ -1954,12 +1964,8 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
19541964
}
19551965

19561966
public MongoDbPropertyValueProvider withContext(ConversionContext context) {
1957-
if (context == this.context) {
1958-
return this;
1959-
}
1960-
1961-
return new MongoDbPropertyValueProvider(context, accessor, evaluator);
19621967

1968+
return context == this.context ? this : new MongoDbPropertyValueProvider(context, accessor, evaluator);
19631969
}
19641970
}
19651971

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

+66-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.mockito.Mockito.*;
2121
import static org.springframework.data.mongodb.core.DocumentTestUtils.*;
2222

23+
import lombok.Data;
2324
import lombok.EqualsAndHashCode;
2425
import lombok.Getter;
2526
import lombok.RequiredArgsConstructor;
@@ -2689,8 +2690,8 @@ void projectShouldReadNestedProjection() {
26892690
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
26902691
mappingContext);
26912692

2692-
EntityProjection<WithNestedProjection, Person> projection = introspector
2693-
.introspect(WithNestedProjection.class, Person.class);
2693+
EntityProjection<WithNestedProjection, Person> projection = introspector.introspect(WithNestedProjection.class,
2694+
Person.class);
26942695
WithNestedProjection person = converter.project(projection, source);
26952696

26962697
assertThat(person.getAddresses()).extracting(AddressProjection::getStreet).hasSize(1).containsOnly("hwy");
@@ -2714,6 +2715,22 @@ void projectShouldReadProjectionWithNestedEntity() {
27142715
assertThat(person.getAddresses()).extracting(Address::getStreet).hasSize(1).containsOnly("hwy");
27152716
}
27162717

2718+
@Test // GH-3998
2719+
void shouldReadOpenProjection() {
2720+
2721+
org.bson.Document author = new org.bson.Document("firstName", "Walter").append("lastName", "White");
2722+
org.bson.Document book = new org.bson.Document("_id", "foo").append("name", "my-book").append("author", author);
2723+
2724+
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(),
2725+
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
2726+
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
2727+
mappingContext);
2728+
2729+
BookProjection projection = converter.project(introspector.introspect(BookProjection.class, Book.class), book);
2730+
2731+
assertThat(projection.getName()).isEqualTo("my-book by Walter White");
2732+
}
2733+
27172734
static class GenericType<T> {
27182735
T content;
27192736
}
@@ -3438,11 +3455,56 @@ static class WithFieldWrite {
34383455
@org.springframework.data.mongodb.core.mapping.Field(
34393456
write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Integer writeAlways;
34403457

3441-
@org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field(
3458+
@org.springframework.data.mongodb.core.mapping.DBRef
3459+
@org.springframework.data.mongodb.core.mapping.Field(
34423460
write = org.springframework.data.mongodb.core.mapping.Field.Write.NON_NULL) Person writeNonNullPerson;
34433461

3444-
@org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field(
3462+
@org.springframework.data.mongodb.core.mapping.DBRef
3463+
@org.springframework.data.mongodb.core.mapping.Field(
34453464
write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Person writeAlwaysPerson;
34463465

34473466
}
3467+
3468+
interface BookProjection {
3469+
3470+
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
3471+
String getName();
3472+
}
3473+
3474+
@Data
3475+
static class Book {
3476+
3477+
@Id String id;
3478+
3479+
String name;
3480+
3481+
Author author = new Author();
3482+
3483+
public Book() {}
3484+
3485+
public Book(String id, String name, Author author) {
3486+
this.id = id;
3487+
this.name = name;
3488+
this.author = author;
3489+
}
3490+
}
3491+
3492+
static class Author {
3493+
3494+
@Id String id;
3495+
3496+
String firstName;
3497+
3498+
String lastName;
3499+
3500+
public Author() {}
3501+
3502+
public Author(String id, String firstName, String lastName) {
3503+
this.id = id;
3504+
this.firstName = firstName;
3505+
this.lastName = lastName;
3506+
}
3507+
3508+
}
3509+
34483510
}

0 commit comments

Comments
 (0)