From 41b700f89f5ffb38f8f8c3e14eae7d62821662de Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 12 Jun 2017 13:33:04 +0200 Subject: [PATCH] DATACASS-463 - Instantiate primary key class with converted arguments. We now apply value conversion before instantiating primary key classes to create instances with the appropriate constructor values. This change allows to declare immutable primary key value objects instead of being forced to use mutable objects. Previously, arguments were passed-thru without conversion causing IllegalArgumentException. --- .../convert/MappingCassandraConverter.java | 28 +++++-- .../data/cassandra/RowMockUtil.java | 20 ++++- .../MappingCassandraConverterUnitTests.java | 76 +++++++++++++------ 3 files changed, 88 insertions(+), 36 deletions(-) diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java index fe6abab7b..aa8ad69a0 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java @@ -50,7 +50,7 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; import org.springframework.data.mapping.model.MappingException; -import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.SpELContext; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -183,17 +183,24 @@ public class MappingCassandraConverter extends AbstractCassandraConverter CassandraUDTValueProvider valueProvider = new CassandraUDTValueProvider(udtValue, CodecRegistry.DEFAULT_INSTANCE, expressionEvaluator); - CassandraPersistentEntityParameterValueProvider parameterProvider = new CassandraPersistentEntityParameterValueProvider( - entity, new MappingAndConvertingValueProvider(valueProvider), null); + PersistentEntityParameterValueProvider parameterValueProvider = getParameterValueProvider( + entity, valueProvider); EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); - S instance = instantiator.createInstance(entity, parameterProvider); + S instance = instantiator.createInstance(entity, parameterValueProvider); readProperties(entity, valueProvider, getConvertingAccessor(instance, entity)); return instance; } + private PersistentEntityParameterValueProvider getParameterValueProvider( + CassandraPersistentEntity entity, CassandraValueProvider valueProvider) { + + return new PersistentEntityParameterValueProvider(entity, + new MappingAndConvertingValueProvider(valueProvider), null); + } + protected void readPropertiesFromRow(CassandraPersistentEntity entity, CassandraRowValueProvider row, PersistentPropertyAccessor propertyAccessor) { @@ -259,10 +266,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter @SuppressWarnings("unused") protected Object instantiatePrimaryKey(CassandraPersistentEntity entity, CassandraPersistentProperty keyProperty, - PropertyValueProvider propertyProvider) { + CassandraValueProvider propertyProvider) { return instantiators.getInstantiatorFor(entity).createInstance(entity, - new CassandraPersistentEntityParameterValueProvider(entity, propertyProvider, null)); + getParameterValueProvider(entity, propertyProvider)); } /* (non-Javadoc) @@ -852,8 +859,13 @@ public class MappingCassandraConverter extends AbstractCassandraConverter * @return the return value, may be {@literal null}. */ @SuppressWarnings("unchecked") - private Object getReadValue(PropertyValueProvider row, - CassandraPersistentProperty property) { + private Object getReadValue(CassandraValueProvider row, CassandraPersistentProperty property) { + + if (property.isCompositePrimaryKey()) { + + CassandraPersistentEntity keyEntity = mappingContext.getPersistentEntity(property); + return instantiatePrimaryKey(keyEntity, property, row); + } Object obj = row.getPropertyValue(property); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/RowMockUtil.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/RowMockUtil.java index 5966dd605..c7791e36a 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/RowMockUtil.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/RowMockUtil.java @@ -29,7 +29,7 @@ import com.datastax.driver.core.Row; /** * Utility to mock a Cassandra {@link Row}. - * + * * @author Mark Paluch */ public class RowMockUtil { @@ -37,7 +37,7 @@ public class RowMockUtil { /** * Creates a new {@link Row} mock using the given {@code columns}. Each column carries a name, value and data type so * users of {@link Row} can use most of the methods. - * + * * @param columns * @return */ @@ -122,12 +122,26 @@ public class RowMockUtil { } }); + Answer answer = new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return columns[(Integer) invocation.getArguments()[0]].value; + } + }; + + when(rowMock.getString(anyInt())).thenAnswer(answer); + when(rowMock.getDate(anyInt())).thenAnswer(answer); + when(rowMock.getBool(anyInt())).thenAnswer(answer); + when(rowMock.getInet(anyInt())).thenAnswer(answer); + when(rowMock.getTimestamp(anyInt())).thenAnswer(answer); + when(rowMock.getUUID(anyInt())).thenAnswer(answer); + return rowMock; } /** * Creates a new {@link Column} to be used with {@link RowMockUtil#newRowMock(Column...)}. - * + * * @param name must not be empty or {@link null}. * @param value can be {@literal null}. * @param type must not be {@literal null}. diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java index e9bb49045..469431a7c 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java @@ -17,11 +17,12 @@ package org.springframework.data.cassandra.convert; import static org.assertj.core.api.Assertions.*; import static org.junit.Assume.*; -import static org.mockito.Mockito.*; import static org.springframework.data.cassandra.RowMockUtil.*; import static org.springframework.data.cassandra.repository.support.BasicMapId.*; import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.Value; import java.io.Serializable; import java.math.BigDecimal; @@ -46,9 +47,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; import org.springframework.cassandra.core.PrimaryKeyType; import org.springframework.core.SpringVersion; import org.springframework.core.convert.ConverterNotFoundException; @@ -90,14 +88,13 @@ import com.datastax.driver.core.querybuilder.Update.Assignments; * @soundtrack Outlandich - Dont Leave Me Feat Cyt (Sun Kidz Electrocore Mix) */ @SuppressWarnings("Since15") -@RunWith(MockitoJUnitRunner.class) public class MappingCassandraConverterUnitTests { private static final Version VERSION_4_3 = Version.parse("4.3"); @Rule public final ExpectedException expectedException = ExpectedException.none(); - @Mock private Row rowMock; + Row rowMock; private CassandraMappingContext mappingContext; private MappingCassandraConverter mappingCassandraConverter; @@ -258,7 +255,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadStringCorrectly() { - when(rowMock.getString(0)).thenReturn("foo"); + rowMock = RowMockUtil.newRowMock(column("foo", "foo", DataType.varchar())); String result = mappingCassandraConverter.readRow(String.class, rowMock); @@ -268,7 +265,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadIntegerCorrectly() { - when(rowMock.getObject(0)).thenReturn(2); + rowMock = RowMockUtil.newRowMock(column("foo", 2, DataType.varint())); Integer result = mappingCassandraConverter.readRow(Integer.class, rowMock); @@ -278,7 +275,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadLongCorrectly() { - when(rowMock.getObject(0)).thenReturn(2); + rowMock = RowMockUtil.newRowMock(column("foo", 2, DataType.varint())); Long result = mappingCassandraConverter.readRow(Long.class, rowMock); @@ -288,7 +285,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadDoubleCorrectly() { - when(rowMock.getObject(0)).thenReturn(2D); + rowMock = RowMockUtil.newRowMock(column("foo", 2D, DataType.cdouble())); Double result = mappingCassandraConverter.readRow(Double.class, rowMock); @@ -298,7 +295,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadFloatCorrectly() { - when(rowMock.getObject(0)).thenReturn(2F); + rowMock = RowMockUtil.newRowMock(column("foo", 2F, DataType.cdouble())); Float result = mappingCassandraConverter.readRow(Float.class, rowMock); @@ -308,7 +305,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadBigIntegerCorrectly() { - when(rowMock.getObject(0)).thenReturn(BigInteger.valueOf(2)); + rowMock = RowMockUtil.newRowMock(column("foo", BigInteger.valueOf(2), DataType.bigint())); BigInteger result = mappingCassandraConverter.readRow(BigInteger.class, rowMock); @@ -318,7 +315,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadBigDecimalCorrectly() { - when(rowMock.getObject(0)).thenReturn(BigDecimal.valueOf(2)); + rowMock = RowMockUtil.newRowMock(column("foo", BigDecimal.valueOf(2), DataType.decimal())); BigDecimal result = mappingCassandraConverter.readRow(BigDecimal.class, rowMock); @@ -330,7 +327,7 @@ public class MappingCassandraConverterUnitTests { UUID uuid = UUID.randomUUID(); - when(rowMock.getUUID(0)).thenReturn(uuid); + rowMock = RowMockUtil.newRowMock(column("foo", uuid, DataType.uuid())); UUID result = mappingCassandraConverter.readRow(UUID.class, rowMock); @@ -342,7 +339,7 @@ public class MappingCassandraConverterUnitTests { InetAddress localHost = InetAddress.getLocalHost(); - when(rowMock.getInet(0)).thenReturn(localHost); + rowMock = RowMockUtil.newRowMock(column("foo", localHost, DataType.inet())); InetAddress result = mappingCassandraConverter.readRow(InetAddress.class, rowMock); @@ -354,7 +351,7 @@ public class MappingCassandraConverterUnitTests { Date date = new Date(1); - when(rowMock.getTimestamp(0)).thenReturn(date); + rowMock = RowMockUtil.newRowMock(column("foo", date, DataType.timestamp())); Date result = mappingCassandraConverter.readRow(Date.class, rowMock); @@ -366,7 +363,7 @@ public class MappingCassandraConverterUnitTests { LocalDate date = LocalDate.fromDaysSinceEpoch(1234); - when(rowMock.getDate(0)).thenReturn(date); + rowMock = RowMockUtil.newRowMock(column("foo", date, DataType.date())); LocalDate result = mappingCassandraConverter.readRow(LocalDate.class, rowMock); @@ -376,7 +373,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-280 public void shouldReadBooleanCorrectly() { - when(rowMock.getBool(0)).thenReturn(true); + rowMock = RowMockUtil.newRowMock(column("foo", true, DataType.cboolean())); Boolean result = mappingCassandraConverter.readRow(Boolean.class, rowMock); @@ -389,7 +386,7 @@ public class MappingCassandraConverterUnitTests { LocalDateTime now = LocalDateTime.now(); Instant instant = now.toInstant(ZoneOffset.UTC); - Row rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), + rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), column("localdate", Date.from(instant), DataType.timestamp())); TypeWithLocalDate result = mappingCassandraConverter.readRow(TypeWithLocalDate.class, rowMock); @@ -472,7 +469,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-296 public void shouldReadLocalDateTimeUsingCassandraDateCorrectly() { - Row rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), + rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), column("localDate", LocalDate.fromYearMonthDay(2010, 7, 4), DataType.date())); TypeWithLocalDateMappedToDate result = mappingCassandraConverter.readRow(TypeWithLocalDateMappedToDate.class, @@ -516,7 +513,7 @@ public class MappingCassandraConverterUnitTests { LocalDateTime now = LocalDateTime.now(); Instant instant = now.toInstant(ZoneOffset.UTC); - Row rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), + rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), column("localDateTime", Date.from(instant), DataType.timestamp())); TypeWithLocalDate result = mappingCassandraConverter.readRow(TypeWithLocalDate.class, rowMock); @@ -532,7 +529,7 @@ public class MappingCassandraConverterUnitTests { LocalDateTime now = LocalDateTime.now(); Instant instant = now.toInstant(ZoneOffset.UTC); - Row rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), + rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), column("instant", Date.from(instant), DataType.timestamp())); TypeWithInstant result = mappingCassandraConverter.readRow(TypeWithInstant.class, rowMock); @@ -544,7 +541,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-296 public void shouldReadZoneIdCorrectly() { - Row rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), + rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), column("zoneId", "Europe/Paris", DataType.varchar())); TypeWithZoneId result = mappingCassandraConverter.readRow(TypeWithZoneId.class, rowMock); @@ -556,7 +553,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-296 public void shouldReadJodaLocalDateTimeUsingCassandraDateCorrectly() { - Row rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), + rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), column("localDate", LocalDate.fromYearMonthDay(2010, 7, 4), DataType.date())); TypeWithJodaLocalDateMappedToDate result = mappingCassandraConverter @@ -597,7 +594,7 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-296 public void shouldReadThreeTenBpLocalDateTimeUsingCassandraDateCorrectly() { - Row rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), + rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataType.ascii()), column("localDate", LocalDate.fromYearMonthDay(2010, 7, 4), DataType.date())); TypeWithThreeTenBpLocalDateMappedToDate result = mappingCassandraConverter @@ -818,6 +815,19 @@ public class MappingCassandraConverterUnitTests { assertThat(getWherePredicates(delete)).containsEntry("lastname", "White"); } + @Test // DATACASS-463 + public void shouldReadTypeWithCompositePrimaryKeyCorrectly() { + + // condition, localDate + Row row = RowMockUtil.newRowMock(column("condition", "MINT", DataType.varchar()), + column("localdate", LocalDate.fromYearMonthDay(2017, 1, 2), DataType.date())); + + TypeWithEnumAndLocalDateKey result = mappingCassandraConverter.read(TypeWithEnumAndLocalDateKey.class, row); + + assertThat(result.id.condition).isEqualTo(Condition.MINT); + assertThat(result.id.localDate).isEqualTo(java.time.LocalDate.of(2017, 1, 2)); + } + @Test // DATACASS-308 public void shouldWriteWhereConditionForTypeWithPkClassKeyUsingMapId() { @@ -990,6 +1000,22 @@ public class MappingCassandraConverterUnitTests { } } + @PrimaryKeyClass + @RequiredArgsConstructor + @Value + public static class EnumAndDateCompositePrimaryKey implements Serializable { + + @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED) private final Condition condition; + + @PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.PARTITIONED) private final java.time.LocalDate localDate; + } + + @RequiredArgsConstructor + public static class TypeWithEnumAndLocalDateKey { + + @PrimaryKey private final EnumAndDateCompositePrimaryKey id; + } + @Table public static class EnumPrimaryKey {