Browse Source

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
pull/3999/head
Mark Paluch 4 years ago
parent
commit
7ce21431a9
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
  2. 70
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

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

@ -49,7 +49,16 @@ import org.springframework.core.convert.support.DefaultConversionService; @@ -49,7 +49,16 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.annotation.Reference;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.*;
import org.springframework.data.mapping.AccessOptions;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@ -407,7 +416,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -407,7 +416,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
EntityProjection<?, ?> property = returnedTypeDescriptor.findProperty(name);
if (property == null) {
return super.forProperty(name);
return new ConversionContext(conversions, path, MappingMongoConverter.this::readDocument, collectionConverter,
mapConverter, dbRefConverter, elementConverter);
}
return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter,
@ -1954,12 +1964,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App @@ -1954,12 +1964,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
public MongoDbPropertyValueProvider withContext(ConversionContext context) {
if (context == this.context) {
return this;
}
return new MongoDbPropertyValueProvider(context, accessor, evaluator);
return context == this.context ? this : new MongoDbPropertyValueProvider(context, accessor, evaluator);
}
}

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

@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*; @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DocumentTestUtils.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@ -2689,8 +2690,8 @@ class MappingMongoConverterUnitTests { @@ -2689,8 +2690,8 @@ class MappingMongoConverterUnitTests {
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
mappingContext);
EntityProjection<WithNestedProjection, Person> projection = introspector
.introspect(WithNestedProjection.class, Person.class);
EntityProjection<WithNestedProjection, Person> projection = introspector.introspect(WithNestedProjection.class,
Person.class);
WithNestedProjection person = converter.project(projection, source);
assertThat(person.getAddresses()).extracting(AddressProjection::getStreet).hasSize(1).containsOnly("hwy");
@ -2714,6 +2715,22 @@ class MappingMongoConverterUnitTests { @@ -2714,6 +2715,22 @@ class MappingMongoConverterUnitTests {
assertThat(person.getAddresses()).extracting(Address::getStreet).hasSize(1).containsOnly("hwy");
}
@Test // GH-3998
void shouldReadOpenProjection() {
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);
BookProjection projection = converter.project(introspector.introspect(BookProjection.class, Book.class), book);
assertThat(projection.getName()).isEqualTo("my-book by Walter White");
}
static class GenericType<T> {
T content;
}
@ -3438,11 +3455,56 @@ class MappingMongoConverterUnitTests { @@ -3438,11 +3455,56 @@ class MappingMongoConverterUnitTests {
@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;
}
interface BookProjection {
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
String getName();
}
@Data
static class Book {
@Id String id;
String name;
Author author = new Author();
public Book() {}
public Book(String id, String name, Author author) {
this.id = id;
this.name = name;
this.author = author;
}
}
static class Author {
@Id String id;
String firstName;
String lastName;
public Author() {}
public Author(String id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
}
}

Loading…
Cancel
Save