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 #1240
This commit is contained in:
@@ -27,6 +27,7 @@ import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -39,7 +40,16 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.cassandra.core.mapping.*;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded.OnEmpty;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.*;
|
||||
import org.springframework.data.mapping.AccessOptions;
|
||||
import org.springframework.data.mapping.InstanceCreatorMetadata;
|
||||
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.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
|
||||
@@ -1054,8 +1064,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
|
||||
if (value == null || target == null
|
||||
|| ClassUtils.isAssignableValue(target, value)) {
|
||||
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1308,10 +1317,9 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
final ValueConverter<Object> elementConverter;
|
||||
|
||||
public ConversionContext(org.springframework.data.convert.CustomConversions conversions,
|
||||
ContainerValueConverter<Row> rowConverter,
|
||||
ContainerValueConverter<TupleValue> tupleConverter, ContainerValueConverter<UdtValue> udtConverter,
|
||||
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Map<?, ?>> mapConverter,
|
||||
ValueConverter<Object> elementConverter) {
|
||||
ContainerValueConverter<Row> rowConverter, ContainerValueConverter<TupleValue> tupleConverter,
|
||||
ContainerValueConverter<UdtValue> udtConverter, ContainerValueConverter<Collection<?>> collectionConverter,
|
||||
ContainerValueConverter<Map<?, ?>> mapConverter, ValueConverter<Object> elementConverter) {
|
||||
this.conversions = conversions;
|
||||
this.rowConverter = rowConverter;
|
||||
this.tupleConverter = tupleConverter;
|
||||
@@ -1577,7 +1585,9 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
|
||||
EntityProjection<?, ?> property = projection.findProperty(name);
|
||||
if (property == null) {
|
||||
return super.forProperty(name);
|
||||
return new ConversionContext(conversions, MappingCassandraConverter.this::doReadRow,
|
||||
MappingCassandraConverter.this::doReadTupleValue, MappingCassandraConverter.this::doReadUdtValue,
|
||||
collectionConverter, mapConverter, elementConverter);
|
||||
}
|
||||
|
||||
return new ProjectingConversionContext(conversions, rowConverter, tupleConverter, udtConverter,
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.springframework.data.cassandra.domain.TypeWithKeyClass;
|
||||
import org.springframework.data.cassandra.domain.TypeWithMapId;
|
||||
import org.springframework.data.cassandra.domain.User;
|
||||
import org.springframework.data.cassandra.domain.UserToken;
|
||||
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
|
||||
import org.springframework.data.cassandra.test.util.RowMockUtil;
|
||||
import org.springframework.data.projection.EntityProjection;
|
||||
import org.springframework.data.projection.EntityProjectionIntrospector;
|
||||
@@ -67,6 +68,7 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
import com.datastax.oss.driver.api.core.cql.Row;
|
||||
import com.datastax.oss.driver.api.core.data.UdtValue;
|
||||
import com.datastax.oss.driver.api.core.type.DataTypes;
|
||||
import com.datastax.oss.driver.internal.core.data.DefaultTupleValue;
|
||||
import com.datastax.oss.driver.internal.core.type.DefaultTupleType;
|
||||
@@ -1308,9 +1310,8 @@ public class MappingCassandraConverterUnitTests {
|
||||
@CassandraType(type = CassandraType.Name.SET,
|
||||
typeArguments = CassandraType.Name.INT) private Set<Condition> conditionSet;
|
||||
|
||||
@CassandraType(type = CassandraType.Name.MAP,
|
||||
typeArguments = { CassandraType.Name.INT,
|
||||
CassandraType.Name.INT }) private Map<Condition, Condition> conditionMap;
|
||||
@CassandraType(type = CassandraType.Name.MAP, typeArguments = { CassandraType.Name.INT,
|
||||
CassandraType.Name.INT }) private Map<Condition, Condition> conditionMap;
|
||||
|
||||
}
|
||||
|
||||
@@ -1481,7 +1482,8 @@ public class MappingCassandraConverterUnitTests {
|
||||
Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("prefixage", 30, DataTypes.INT),
|
||||
column("prefixfirstname", "fn", DataTypes.TEXT));
|
||||
|
||||
WithPrefixedNullableEmbeddedType target = mappingCassandraConverter.read(WithPrefixedNullableEmbeddedType.class, source);
|
||||
WithPrefixedNullableEmbeddedType target = mappingCassandraConverter.read(WithPrefixedNullableEmbeddedType.class,
|
||||
source);
|
||||
assertThat(target.nested).isEqualTo(new EmbeddedWithSimpleTypes("fn", 30, null));
|
||||
}
|
||||
|
||||
@@ -1514,6 +1516,29 @@ public class MappingCassandraConverterUnitTests {
|
||||
assertThat(target.theJson.get("hello")).isEqualTo("world");
|
||||
}
|
||||
|
||||
@Test // GH-1240
|
||||
void shouldReadOpenProjectionWithNestedObject() {
|
||||
|
||||
com.datastax.oss.driver.api.core.type.UserDefinedType authorType = UserDefinedTypeBuilder.forName("author")
|
||||
.withField("firstName", DataTypes.TEXT).withField("lastName", DataTypes.TEXT).build();
|
||||
|
||||
UdtValue udtValue = authorType.newValue().setString("firstName", "Walter").setString("lastName", "White");
|
||||
|
||||
Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("name", "my-book", DataTypes.INT),
|
||||
column("author", udtValue, authorType));
|
||||
|
||||
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(
|
||||
mappingCassandraConverter.getProjectionFactory(),
|
||||
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
|
||||
.and((target, underlyingType) -> !mappingCassandraConverter.getCustomConversions().isSimpleType(target)),
|
||||
mappingContext);
|
||||
|
||||
BookProjection projection = mappingCassandraConverter
|
||||
.project(introspector.introspect(BookProjection.class, Book.class), source);
|
||||
|
||||
assertThat(projection.getName()).isEqualTo("my-book by Walter White");
|
||||
}
|
||||
|
||||
static class TypeWithJsonObject {
|
||||
|
||||
JSONObject theJson;
|
||||
@@ -1542,4 +1567,33 @@ public class MappingCassandraConverterUnitTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@UserDefinedType
|
||||
static class Author {
|
||||
|
||||
@Id String id;
|
||||
|
||||
String firstName;
|
||||
|
||||
String lastName;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user