Skip to content

Commit 1009491

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 05730de commit 1009491

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
@@ -39,7 +39,16 @@
3939
import org.springframework.data.annotation.Reference;
4040
import org.springframework.data.convert.CustomConversions;
4141
import org.springframework.data.convert.TypeMapper;
42-
import org.springframework.data.mapping.*;
42+
import org.springframework.data.mapping.AccessOptions;
43+
import org.springframework.data.mapping.Association;
44+
import org.springframework.data.mapping.MappingException;
45+
import org.springframework.data.mapping.Parameter;
46+
import org.springframework.data.mapping.PersistentEntity;
47+
import org.springframework.data.mapping.PersistentProperty;
48+
import org.springframework.data.mapping.PersistentPropertyAccessor;
49+
import org.springframework.data.mapping.PersistentPropertyPath;
50+
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
51+
import org.springframework.data.mapping.PreferredConstructor;
4352
import org.springframework.data.mapping.callback.EntityCallbacks;
4453
import org.springframework.data.mapping.context.MappingContext;
4554
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@@ -385,7 +394,8 @@ public ConversionContext forProperty(String name) {
385394

386395
EntityProjection<?, ?> property = returnedTypeDescriptor.findProperty(name);
387396
if (property == null) {
388-
return super.forProperty(name);
397+
return new ConversionContext(conversions, path, MappingMongoConverter.this::readDocument, collectionConverter,
398+
mapConverter, dbRefConverter, elementConverter);
389399
}
390400

391401
return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter,
@@ -1902,12 +1912,8 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
19021912
}
19031913

19041914
public MongoDbPropertyValueProvider withContext(ConversionContext context) {
1905-
if (context == this.context) {
1906-
return this;
1907-
}
1908-
1909-
return new MongoDbPropertyValueProvider(context, accessor, evaluator);
19101915

1916+
return context == this.context ? this : new MongoDbPropertyValueProvider(context, accessor, evaluator);
19111917
}
19121918
}
19131919

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;
@@ -2664,8 +2665,8 @@ void projectShouldReadNestedProjection() {
26642665
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
26652666
mappingContext);
26662667

2667-
EntityProjection<WithNestedProjection, Person> projection = introspector
2668-
.introspect(WithNestedProjection.class, Person.class);
2668+
EntityProjection<WithNestedProjection, Person> projection = introspector.introspect(WithNestedProjection.class,
2669+
Person.class);
26692670
WithNestedProjection person = converter.project(projection, source);
26702671

26712672
assertThat(person.getAddresses()).extracting(AddressProjection::getStreet).hasSize(1).containsOnly("hwy");
@@ -2689,6 +2690,22 @@ void projectShouldReadProjectionWithNestedEntity() {
26892690
assertThat(person.getAddresses()).extracting(Address::getStreet).hasSize(1).containsOnly("hwy");
26902691
}
26912692

2693+
@Test // GH-3998
2694+
void shouldReadOpenProjection() {
2695+
2696+
org.bson.Document author = new org.bson.Document("firstName", "Walter").append("lastName", "White");
2697+
org.bson.Document book = new org.bson.Document("_id", "foo").append("name", "my-book").append("author", author);
2698+
2699+
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(),
2700+
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
2701+
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
2702+
mappingContext);
2703+
2704+
BookProjection projection = converter.project(introspector.introspect(BookProjection.class, Book.class), book);
2705+
2706+
assertThat(projection.getName()).isEqualTo("my-book by Walter White");
2707+
}
2708+
26922709
static class GenericType<T> {
26932710
T content;
26942711
}
@@ -3413,11 +3430,56 @@ static class WithFieldWrite {
34133430
@org.springframework.data.mongodb.core.mapping.Field(
34143431
write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Integer writeAlways;
34153432

3416-
@org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field(
3433+
@org.springframework.data.mongodb.core.mapping.DBRef
3434+
@org.springframework.data.mongodb.core.mapping.Field(
34173435
write = org.springframework.data.mongodb.core.mapping.Field.Write.NON_NULL) Person writeNonNullPerson;
34183436

3419-
@org.springframework.data.mongodb.core.mapping.DBRef @org.springframework.data.mongodb.core.mapping.Field(
3437+
@org.springframework.data.mongodb.core.mapping.DBRef
3438+
@org.springframework.data.mongodb.core.mapping.Field(
34203439
write = org.springframework.data.mongodb.core.mapping.Field.Write.ALWAYS) Person writeAlwaysPerson;
34213440

34223441
}
3442+
3443+
interface BookProjection {
3444+
3445+
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
3446+
String getName();
3447+
}
3448+
3449+
@Data
3450+
static class Book {
3451+
3452+
@Id String id;
3453+
3454+
String name;
3455+
3456+
Author author = new Author();
3457+
3458+
public Book() {}
3459+
3460+
public Book(String id, String name, Author author) {
3461+
this.id = id;
3462+
this.name = name;
3463+
this.author = author;
3464+
}
3465+
}
3466+
3467+
static class Author {
3468+
3469+
@Id String id;
3470+
3471+
String firstName;
3472+
3473+
String lastName;
3474+
3475+
public Author() {}
3476+
3477+
public Author(String id, String firstName, String lastName) {
3478+
this.id = id;
3479+
this.firstName = firstName;
3480+
this.lastName = lastName;
3481+
}
3482+
3483+
}
3484+
34233485
}

0 commit comments

Comments
 (0)