DATACASS-400 - Use custom conversion and UDT mapping with entity instantiator.

We now apply custom conversion and UDT mapping to the value provider that is used with entity instantiators. This allows entities to be instantiated with mapped UDTs and using custom conversion with properties that are provided using a constructor.

Previously, columns were not converted which caused a parameter mismatch so entities couldn't be instantiated.
This commit is contained in:
Mark Paluch
2017-02-27 12:51:20 +01:00
parent 3578880a87
commit 0455decf3b
3 changed files with 90 additions and 51 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors
* Copyright 2013-2017 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,23 +17,13 @@ package org.springframework.data.cassandra.convert;
import static org.springframework.data.cassandra.repository.support.BasicMapId.*;
import lombok.AllArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.querybuilder.Update;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,6 +56,19 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.querybuilder.Update;
/**
* {@link CassandraConverter} that uses a {@link MappingContext} to do sophisticated mapping of domain objects to
* {@link Row}.
@@ -136,8 +139,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return getConversionService().convert(row, type);
}
CassandraPersistentEntity<R> persistentEntity = (CassandraPersistentEntity<R>)
getMappingContext().getPersistentEntity(typeInfo);
CassandraPersistentEntity<R> persistentEntity = (CassandraPersistentEntity<R>) getMappingContext()
.getPersistentEntity(typeInfo);
if (persistentEntity == null) {
throw new MappingException(String.format("No mapping metadata found for %s", rawType.getName()));
@@ -160,8 +163,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
DefaultSpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(row, spELContext);
BasicCassandraRowValueProvider rowValueProvider = new BasicCassandraRowValueProvider(row, expressionEvaluator);
CassandraPersistentEntityParameterValueProvider parameterProvider =
new CassandraPersistentEntityParameterValueProvider(entity, rowValueProvider, null);
CassandraPersistentEntityParameterValueProvider parameterProvider = new CassandraPersistentEntityParameterValueProvider(
entity, new MappingAndConvertingValueProvider(rowValueProvider), null);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, parameterProvider);
@@ -175,11 +178,11 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
DefaultSpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(udtValue, spELContext);
CassandraUDTValueProvider valueProvider = new CassandraUDTValueProvider(
udtValue, CodecRegistry.DEFAULT_INSTANCE, expressionEvaluator);
CassandraUDTValueProvider valueProvider = new CassandraUDTValueProvider(udtValue, CodecRegistry.DEFAULT_INSTANCE,
expressionEvaluator);
CassandraPersistentEntityParameterValueProvider parameterProvider =
new CassandraPersistentEntityParameterValueProvider(entity, valueProvider, null);
CassandraPersistentEntityParameterValueProvider parameterProvider = new CassandraPersistentEntityParameterValueProvider(
entity, new MappingAndConvertingValueProvider(valueProvider), null);
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
S instance = instantiator.createInstance(entity, parameterProvider);
@@ -833,4 +836,40 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
private TypeCodec<Object> getCodec(CassandraPersistentProperty property) {
return CodecRegistry.DEFAULT_INSTANCE.codecFor(mappingContext.getDataType(property));
}
/**
* {@link CassandraRowValueProvider} that delegates reads to {@link CassandraValueProvider} applying mapping and
* custom conversion from {@link MappingCassandraConverter}.
*
* @author Mark Paluch
* @since 1.5.1
*/
@AllArgsConstructor
class MappingAndConvertingValueProvider implements CassandraValueProvider {
private final CassandraValueProvider parent;
/* (non-Javadoc)
* @see org.springframework.data.cassandra.convert.CassandraValueProvider#hasProperty(org.springframework.data.cassandra.mapping.CassandraPersistentProperty)
*/
@Override
public boolean hasProperty(CassandraPersistentProperty property) {
return parent.hasProperty(property);
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.model.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public Object getPropertyValue(CassandraPersistentProperty property) {
Object readValue = getReadValue(parent, property);
if(readValue == null || property.getType().isAssignableFrom(readValue.getClass())){
return readValue;
}
return conversionService.convert(readValue, property.getType());
}
}
}

View File

@@ -17,7 +17,9 @@ package org.springframework.data.cassandra.convert;
import static org.assertj.core.api.Assertions.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.util.Arrays;
import java.util.Collections;
@@ -291,11 +293,10 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
assertThat(addressBook.getOtherCurrencies()).hasSize(1).contains(Currency.getInstance("EUR"));
}
@Test // DATACASS-172
@Test // DATACASS-172, DATACASS-400
public void shouldWriteUdtWithCustomConversion() {
Bank bank = new Bank();
bank.setCurrency(Currency.getInstance("EUR"));
Bank bank = new Bank(null, Currency.getInstance("EUR"), null);
Insert insert = QueryBuilder.insertInto("bank");
converter.write(bank, insert);
@@ -316,12 +317,10 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
assertThat(update.toString()).isEqualTo("UPDATE money WHERE currency={currency:'EUR'};");
}
@Test // DATACASS-172
@Test // DATACASS-172, DATACASS-400
public void shouldWriteUdtUpdateAssignmentsWithCustomConversion() {
MoneyTransfer money = new MoneyTransfer();
money.setId("1");
money.setCurrency(Currency.getInstance("EUR"));
MoneyTransfer money = new MoneyTransfer("1", Currency.getInstance("EUR"));
Update update = QueryBuilder.update("money");
converter.write(money, update);
@@ -353,11 +352,10 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
assertThat(delete.toString()).isEqualTo("DELETE FROM money WHERE currency={currency:'EUR'};");
}
@Test // DATACASS-172
@Test // DATACASS-172, DATACASS-400
public void shouldWriteUdtListWithCustomConversion() {
Bank bank = new Bank();
bank.setOtherCurrencies(Collections.singletonList(Currency.getInstance("EUR")));
Bank bank = new Bank(null, null, Collections.singletonList(Currency.getInstance("EUR")));
Insert insert = QueryBuilder.insertInto("bank");
converter.write(bank, insert);
@@ -379,20 +377,14 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
assertThat(car.getEngine().getManufacturer().getName()).isEqualTo("a good one");
}
@Test // DATACASS-172
@Test // DATACASS-172, DATACASS-400
public void shouldWriteNestedUdt() {
session.execute("INSERT INTO car (id, engine) VALUES ('1', {manufacturer: {name:'a good one'}});");
Manufacturer manufacturer = new Manufacturer();
manufacturer.setName("a good one");
Engine engine = new Engine(new Manufacturer("a good one"));
Engine engine = new Engine();
engine.setManufacturer(manufacturer);
Car car = new Car();
car.setId("1");
car.setEngine(engine);
Car car = new Car("1", engine);
Insert insert = QueryBuilder.insertInto("car");
converter.write(car, insert);
@@ -402,7 +394,8 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
}
@Table
@Data
@Getter
@AllArgsConstructor
private static class Bank {
@Id String id;
@@ -416,8 +409,9 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
@Id private Currency currency;
}
@Data
@Table
@AllArgsConstructor
@Getter
public static class MoneyTransfer {
@Id String id;
@@ -426,7 +420,8 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
}
@Table
@Data
@Getter
@AllArgsConstructor
private static class Car {
@Id String id;
@@ -434,13 +429,15 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring
}
@UserDefinedType
@Data
@Getter
@AllArgsConstructor
private static class Engine {
Manufacturer manufacturer;
}
@UserDefinedType
@Data
@Getter
@AllArgsConstructor
private static class Manufacturer {
String name;
}

View File

@@ -21,6 +21,8 @@ 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 java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -482,11 +484,11 @@ public class MappingCassandraConverterUnitTests {
assertThat(result.localDate.getDayOfMonth()).isEqualTo(4);
}
@Test // DATACASS-296
@Test // DATACASS-296, DATACASS-400
public void shouldCreateInsertWithLocalDateUsingCassandraDateCorrectly() {
TypeWithLocalDateMappedToDate typeWithLocalDate = new TypeWithLocalDateMappedToDate();
typeWithLocalDate.localDate = java.time.LocalDate.of(2010, 7, 4);
TypeWithLocalDateMappedToDate typeWithLocalDate = new TypeWithLocalDateMappedToDate(null,
java.time.LocalDate.of(2010, 7, 4));
Insert insert = QueryBuilder.insertInto("table");
@@ -498,8 +500,8 @@ public class MappingCassandraConverterUnitTests {
@Test // DATACASS-296
public void shouldCreateUpdateWithLocalDateUsingCassandraDateCorrectly() {
TypeWithLocalDateMappedToDate typeWithLocalDate = new TypeWithLocalDateMappedToDate();
typeWithLocalDate.localDate = java.time.LocalDate.of(2010, 7, 4);
TypeWithLocalDateMappedToDate typeWithLocalDate = new TypeWithLocalDateMappedToDate(null,
java.time.LocalDate.of(2010, 7, 4));
Update update = QueryBuilder.update("table");
@@ -1042,6 +1044,7 @@ public class MappingCassandraConverterUnitTests {
* Uses Cassandra's {@link Name#DATE} which maps by default to {@link LocalDate}
*/
@Table
@AllArgsConstructor
public static class TypeWithLocalDateMappedToDate {
@PrimaryKey private String id;