Skip to content

Commit d670af5

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 #1240
1 parent 97e2368 commit d670af5

File tree

2 files changed

+76
-12
lines changed

2 files changed

+76
-12
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java

+18-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.apache.commons.logging.Log;
2929
import org.apache.commons.logging.LogFactory;
30+
3031
import org.springframework.beans.BeansException;
3132
import org.springframework.beans.factory.BeanClassLoaderAware;
3233
import org.springframework.context.ApplicationContext;
@@ -39,7 +40,16 @@
3940
import org.springframework.data.cassandra.core.mapping.*;
4041
import org.springframework.data.cassandra.core.mapping.Embedded.OnEmpty;
4142
import org.springframework.data.convert.CustomConversions;
42-
import org.springframework.data.mapping.*;
43+
import org.springframework.data.mapping.AccessOptions;
44+
import org.springframework.data.mapping.InstanceCreatorMetadata;
45+
import org.springframework.data.mapping.MappingException;
46+
import org.springframework.data.mapping.Parameter;
47+
import org.springframework.data.mapping.PersistentEntity;
48+
import org.springframework.data.mapping.PersistentProperty;
49+
import org.springframework.data.mapping.PersistentPropertyAccessor;
50+
import org.springframework.data.mapping.PersistentPropertyPath;
51+
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
52+
import org.springframework.data.mapping.PreferredConstructor;
4353
import org.springframework.data.mapping.context.MappingContext;
4454
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
4555
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
@@ -1054,8 +1064,7 @@ protected Object getPotentiallyConvertedSimpleRead(Object value, TypeInformation
10541064
@SuppressWarnings({ "rawtypes", "unchecked" })
10551065
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
10561066

1057-
if (value == null || target == null
1058-
|| ClassUtils.isAssignableValue(target, value)) {
1067+
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
10591068
return value;
10601069
}
10611070

@@ -1308,10 +1317,9 @@ protected static class ConversionContext {
13081317
final ValueConverter<Object> elementConverter;
13091318

13101319
public ConversionContext(org.springframework.data.convert.CustomConversions conversions,
1311-
ContainerValueConverter<Row> rowConverter,
1312-
ContainerValueConverter<TupleValue> tupleConverter, ContainerValueConverter<UdtValue> udtConverter,
1313-
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Map<?, ?>> mapConverter,
1314-
ValueConverter<Object> elementConverter) {
1320+
ContainerValueConverter<Row> rowConverter, ContainerValueConverter<TupleValue> tupleConverter,
1321+
ContainerValueConverter<UdtValue> udtConverter, ContainerValueConverter<Collection<?>> collectionConverter,
1322+
ContainerValueConverter<Map<?, ?>> mapConverter, ValueConverter<Object> elementConverter) {
13151323
this.conversions = conversions;
13161324
this.rowConverter = rowConverter;
13171325
this.tupleConverter = tupleConverter;
@@ -1577,7 +1585,9 @@ ConversionContext forProperty(String name) {
15771585

15781586
EntityProjection<?, ?> property = projection.findProperty(name);
15791587
if (property == null) {
1580-
return super.forProperty(name);
1588+
return new ConversionContext(conversions, MappingCassandraConverter.this::doReadRow,
1589+
MappingCassandraConverter.this::doReadTupleValue, MappingCassandraConverter.this::doReadUdtValue,
1590+
collectionConverter, mapConverter, elementConverter);
15811591
}
15821592

15831593
return new ProjectingConversionContext(conversions, rowConverter, tupleConverter, udtConverter,

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java

+58-4
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,15 @@
6060
import org.springframework.data.cassandra.domain.TypeWithMapId;
6161
import org.springframework.data.cassandra.domain.User;
6262
import org.springframework.data.cassandra.domain.UserToken;
63+
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
6364
import org.springframework.data.cassandra.test.util.RowMockUtil;
6465
import org.springframework.data.projection.EntityProjection;
6566
import org.springframework.data.projection.EntityProjectionIntrospector;
6667
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
6768

6869
import com.datastax.oss.driver.api.core.CqlIdentifier;
6970
import com.datastax.oss.driver.api.core.cql.Row;
71+
import com.datastax.oss.driver.api.core.data.UdtValue;
7072
import com.datastax.oss.driver.api.core.type.DataTypes;
7173
import com.datastax.oss.driver.internal.core.data.DefaultTupleValue;
7274
import com.datastax.oss.driver.internal.core.type.DefaultTupleType;
@@ -1308,9 +1310,8 @@ private static class TypeWithConvertedCollections {
13081310
@CassandraType(type = CassandraType.Name.SET,
13091311
typeArguments = CassandraType.Name.INT) private Set<Condition> conditionSet;
13101312

1311-
@CassandraType(type = CassandraType.Name.MAP,
1312-
typeArguments = { CassandraType.Name.INT,
1313-
CassandraType.Name.INT }) private Map<Condition, Condition> conditionMap;
1313+
@CassandraType(type = CassandraType.Name.MAP, typeArguments = { CassandraType.Name.INT,
1314+
CassandraType.Name.INT }) private Map<Condition, Condition> conditionMap;
13141315

13151316
}
13161317

@@ -1481,7 +1482,8 @@ void readPrefixedEmbeddedType() {
14811482
Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("prefixage", 30, DataTypes.INT),
14821483
column("prefixfirstname", "fn", DataTypes.TEXT));
14831484

1484-
WithPrefixedNullableEmbeddedType target = mappingCassandraConverter.read(WithPrefixedNullableEmbeddedType.class, source);
1485+
WithPrefixedNullableEmbeddedType target = mappingCassandraConverter.read(WithPrefixedNullableEmbeddedType.class,
1486+
source);
14851487
assertThat(target.nested).isEqualTo(new EmbeddedWithSimpleTypes("fn", 30, null));
14861488
}
14871489

@@ -1514,6 +1516,29 @@ void shouldApplyCustomConverterToMapLikeType() {
15141516
assertThat(target.theJson.get("hello")).isEqualTo("world");
15151517
}
15161518

1519+
@Test // GH-1240
1520+
void shouldReadOpenProjectionWithNestedObject() {
1521+
1522+
com.datastax.oss.driver.api.core.type.UserDefinedType authorType = UserDefinedTypeBuilder.forName("author")
1523+
.withField("firstName", DataTypes.TEXT).withField("lastName", DataTypes.TEXT).build();
1524+
1525+
UdtValue udtValue = authorType.newValue().setString("firstName", "Walter").setString("lastName", "White");
1526+
1527+
Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("name", "my-book", DataTypes.INT),
1528+
column("author", udtValue, authorType));
1529+
1530+
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(
1531+
mappingCassandraConverter.getProjectionFactory(),
1532+
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
1533+
.and((target, underlyingType) -> !mappingCassandraConverter.getCustomConversions().isSimpleType(target)),
1534+
mappingContext);
1535+
1536+
BookProjection projection = mappingCassandraConverter
1537+
.project(introspector.introspect(BookProjection.class, Book.class), source);
1538+
1539+
assertThat(projection.getName()).isEqualTo("my-book by Walter White");
1540+
}
1541+
15171542
static class TypeWithJsonObject {
15181543

15191544
JSONObject theJson;
@@ -1542,4 +1567,33 @@ public String convert(JSONObject source) {
15421567
}
15431568

15441569
}
1570+
1571+
interface BookProjection {
1572+
1573+
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
1574+
String getName();
1575+
}
1576+
1577+
@Data
1578+
static class Book {
1579+
1580+
@Id String id;
1581+
1582+
String name;
1583+
1584+
Author author = new Author();
1585+
1586+
}
1587+
1588+
@Data
1589+
@UserDefinedType
1590+
static class Author {
1591+
1592+
@Id String id;
1593+
1594+
String firstName;
1595+
1596+
String lastName;
1597+
1598+
}
15451599
}

0 commit comments

Comments
 (0)