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 {