DATACASS-260 - Enable mapping for enum-typed properties.

We now support out-of-the box mapping for enum-typed properties in domain types and primary keys. Enums are mapped using their name. Ordinal-based enum mapping is not supported but can be achieved by registering a custom converter.

Original pull request: #47.
This commit is contained in:
Mark Paluch
2016-03-18 10:26:47 +01:00
parent d556af7b33
commit 5e09ef5adf
6 changed files with 434 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors
* Copyright 2013-2016 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.
@@ -59,6 +59,7 @@ import com.datastax.driver.core.querybuilder.Update;
* @author Alex Shvid
* @author Matthew T. Adams
* @author Oliver Gierke
* @author Mark Paluch
*/
public class MappingCassandraConverter extends AbstractCassandraConverter
implements CassandraConverter, ApplicationContextAware, BeanClassLoaderAware {
@@ -239,7 +240,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
@Override
public void doWithPersistentProperty(CassandraPersistentProperty prop) {
Object value = accessor.getProperty(prop, prop.getType());
Object value = accessor.getProperty(prop, prop.isCompositePrimaryKey() ? prop.getType() : prop.getDataType().asJavaClass());
log.debug("prop.type -> " + prop.getType().getName());
log.debug("prop.value -> " + value);
@@ -272,7 +273,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
@Override
public void doWithPersistentProperty(CassandraPersistentProperty prop) {
Object value = accessor.getProperty(prop, prop.getType());
Object value = accessor.getProperty(prop, prop.isCompositePrimaryKey() ? prop.getType() : prop.getDataType().asJavaClass());
if (prop.isCompositePrimaryKey()) {
CassandraPersistentEntity<?> keyEntity = prop.getCompositePrimaryKeyEntity();
@@ -296,15 +297,17 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
writeDeleteWhereFromWrapper(getConvertingAccessor(object, entity), where, entity);
}
protected void writeDeleteWhereFromWrapper(final PersistentPropertyAccessor accessor, final Where where,
protected void writeDeleteWhereFromWrapper(final ConvertingPropertyAccessor accessor, final Where where,
CassandraPersistentEntity<?> entity) {
// if the entity itself if a composite primary key, then we've recursed, so just add columns & return
if (entity.isCompositePrimaryKey()) {
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@Override
public void doWithPersistentProperty(CassandraPersistentProperty p) {
where.and(QueryBuilder.eq(p.getColumnName().toCql(), accessor.getProperty(p)));
public void doWithPersistentProperty(CassandraPersistentProperty prop) {
Object value = accessor.getProperty(prop, prop.getDataType().asJavaClass());
where.and(QueryBuilder.eq(prop.getColumnName().toCql(), value));
}
});
return;
@@ -364,7 +367,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
CassandraPersistentProperty idProperty = entity.getIdProperty();
if (idProperty != null) {
return wrapper.getProperty(entity.getIdProperty(), idProperty.getType());
return wrapper.getProperty(entity.getIdProperty(), idProperty.isCompositePrimaryKey() ? idProperty.getType() : idProperty.getDataType().asJavaClass());
}
// if the class doesn't have an id property, then it's using MapId

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -33,6 +33,7 @@ import com.datastax.driver.core.DataType;
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author Mark Paluch
*/
public class CassandraSimpleTypeHolder extends SimpleTypeHolder {
@@ -82,6 +83,11 @@ public class CassandraSimpleTypeHolder extends SimpleTypeHolder {
}
public static DataType getDataTypeFor(Class<?> javaClass) {
if (javaClass.isEnum()) {
return DataType.varchar();
}
return dataTypesByJavaClass.get(javaClass);
}

View File

@@ -0,0 +1,361 @@
/*
* Copyright 2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.test.integration.convert;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.mapping.PrimaryKey;
import org.springframework.data.cassandra.mapping.PrimaryKeyClass;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.test.util.ReflectionTestUtils;
import com.datastax.driver.core.DataType.Name;
import com.datastax.driver.core.querybuilder.Assignment;
import com.datastax.driver.core.querybuilder.BuiltStatement;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.Delete.Where;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Update;
import com.datastax.driver.core.querybuilder.Update.Assignments;
/**
* Tests for {@link MappingCassandraConverter}.
*
* @author Mark Paluch
* @soundtrack Outlandich - Dont Leave Me Feat Cyt (Sun Kidz Electrocore Mix)
*/
public class MappingCassandraConverterTests {
@Rule public final ExpectedException expectedException = ExpectedException.none();
private MappingCassandraConverter mappingCassandraConverter = new MappingCassandraConverter();
/**
* @see DATACASS-260
*/
@Test
public void insertEnumShouldMapToString() {
WithEnumColumns withEnumColumns = new WithEnumColumns();
withEnumColumns.setCondition(Condition.MINT);
Insert insert = QueryBuilder.insertInto("table");
mappingCassandraConverter.write(withEnumColumns, insert);
assertThat(getValues(insert), contains((Object) "MINT"));
}
/**
* @see DATACASS-260
*/
@Test
public void insertEnumDoesNotMapToOrdinal() {
expectedException.expect(ConverterNotFoundException.class);
expectedException.expectMessage(allOf(containsString("No converter found"), containsString("java.lang.Integer")));
UnsupportedEnumToOrdinalMapping unsupportedEnumToOrdinalMapping = new UnsupportedEnumToOrdinalMapping();
unsupportedEnumToOrdinalMapping.setAsOrdinal(Condition.MINT);
Insert insert = QueryBuilder.insertInto("table");
mappingCassandraConverter.write(unsupportedEnumToOrdinalMapping, insert);
}
/**
* @see DATACASS-260
*/
@Test
public void insertEnumAsPrimaryKeyShouldMapToString() {
EnumPrimaryKey key = new EnumPrimaryKey();
key.setCondition(Condition.MINT);
Insert insert = QueryBuilder.insertInto("table");
mappingCassandraConverter.write(key, insert);
assertThat(getValues(insert), contains((Object) "MINT"));
}
/**
* @see DATACASS-260
*/
@Test
public void insertEnumInCompositePrimaryKeyShouldMapToString() {
EnumCompositePrimaryKey key = new EnumCompositePrimaryKey();
key.setCondition(Condition.MINT);
CompositeKeyThing composite = new CompositeKeyThing();
composite.setKey(key);
Insert insert = QueryBuilder.insertInto("table");
mappingCassandraConverter.write(composite, insert);
assertThat(getValues(insert), contains((Object) "MINT"));
}
/**
* @see DATACASS-260
*/
@Test
public void updateEnumShouldMapToString() {
WithEnumColumns withEnumColumns = new WithEnumColumns();
withEnumColumns.setCondition(Condition.MINT);
Update update = QueryBuilder.update("table");
mappingCassandraConverter.write(withEnumColumns, update);
assertThat(getAssignmentValues(update), contains((Object) "MINT"));
}
/**
* @see DATACASS-260
*/
@Test
public void updateEnumAsPrimaryKeyShouldMapToString() {
EnumPrimaryKey key = new EnumPrimaryKey();
key.setCondition(Condition.MINT);
Update update = QueryBuilder.update("table");
mappingCassandraConverter.write(key, update);
assertThat(getWhereValues(update), contains((Object) "MINT"));
}
/**
* @see DATACASS-260
*/
@Test
public void updateEnumInCompositePrimaryKeyShouldMapToString() {
EnumCompositePrimaryKey key = new EnumCompositePrimaryKey();
key.setCondition(Condition.MINT);
CompositeKeyThing composite = new CompositeKeyThing();
composite.setKey(key);
Update update = QueryBuilder.update("table");
mappingCassandraConverter.write(composite, update);
assertThat(getWhereValues(update), contains((Object) "MINT"));
}
/**
* @see DATACASS-260
*/
@Test
public void whereEnumAsPrimaryKeyShouldMapToString() {
EnumPrimaryKey key = new EnumPrimaryKey();
key.setCondition(Condition.MINT);
Where where = QueryBuilder.delete().from("table").where();
mappingCassandraConverter.write(key, where);
assertThat(getWhereValues(where), contains((Object) "MINT"));
}
/**
* @see DATACASS-260
*/
@Test
public void whereEnumInCompositePrimaryKeyShouldMapToString() {
EnumCompositePrimaryKey key = new EnumCompositePrimaryKey();
key.setCondition(Condition.MINT);
CompositeKeyThing composite = new CompositeKeyThing();
composite.setKey(key);
Where where = QueryBuilder.delete().from("table").where();
mappingCassandraConverter.write(composite, where);
assertThat(getWhereValues(where), contains((Object) "MINT"));
}
@SuppressWarnings("unchecked")
private List<Object> getValues(Insert statement) {
return (List<Object>) ReflectionTestUtils.getField(statement, "values");
}
@SuppressWarnings("unchecked")
private List<Object> getAssignmentValues(Update statement) {
List<Object> result = new ArrayList<Object>();
Assignments assignments = (Assignments) ReflectionTestUtils.getField(statement, "assignments");
List<Assignment> listOfAssignments = (List<Assignment>) ReflectionTestUtils.getField(assignments, "assignments");
for (Assignment assignment : listOfAssignments) {
result.add(ReflectionTestUtils.getField(assignment, "value"));
}
return result;
}
private List<Object> getWhereValues(Update statement) {
return getWhereValues(statement.where());
}
private List<Object> getWhereValues(BuiltStatement where) {
List<Object> result = new ArrayList<Object>();
List<Clause> clauses = (List<Clause>) ReflectionTestUtils.getField(where, "clauses");
for (Clause clause : clauses) {
result.add(ReflectionTestUtils.getField(clause, "value"));
}
return result;
}
@Table
public static class UnsupportedEnumToOrdinalMapping {
@PrimaryKey private String id;
@CassandraType(type = Name.INT) private Condition asOrdinal;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Condition getAsOrdinal() {
return asOrdinal;
}
public void setAsOrdinal(Condition asOrdinal) {
this.asOrdinal = asOrdinal;
}
}
@Table
public static class WithEnumColumns {
@PrimaryKey private String id;
private Condition condition;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Condition getCondition() {
return condition;
}
public void setCondition(Condition condition) {
this.condition = condition;
}
}
@PrimaryKeyClass
public static class EnumCompositePrimaryKey implements Serializable {
@PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED) private Condition condition;
public EnumCompositePrimaryKey() {}
public EnumCompositePrimaryKey(Condition condition) {
this.condition = condition;
}
public Condition getCondition() {
return condition;
}
public void setCondition(Condition condition) {
this.condition = condition;
}
}
@Table
public static class EnumPrimaryKey {
@PrimaryKey private Condition condition;
public Condition getCondition() {
return condition;
}
public void setCondition(Condition condition) {
this.condition = condition;
}
}
@Table
public static class CompositeKeyThing {
@PrimaryKey private EnumCompositePrimaryKey key;
public CompositeKeyThing() {}
public CompositeKeyThing(EnumCompositePrimaryKey key) {
this.key = key;
}
public EnumCompositePrimaryKey getKey() {
return key;
}
public void setKey(EnumCompositePrimaryKey key) {
this.key = key;
}
}
public static enum Condition {
MINT, USED;
}
}

View File

@@ -36,6 +36,7 @@ public class Book {
private int pages;
private Date saleDate;
private boolean isInStock;
private BookCondition condition;
/**
* @return Returns the isbn.
@@ -121,6 +122,22 @@ public class Book {
this.pages = pages;
}
/**
*
* @param condition The condition to set.
*/
public void setCondition(BookCondition condition) {
this.condition = condition;
}
/**
*
* @return Returns the condition.
*/
public BookCondition getCondition() {
return condition;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@@ -131,6 +148,7 @@ public class Book {
sb.append("tile -> " + title).append("\n");
sb.append("author -> " + author).append("\n");
sb.append("pages -> " + pages).append("\n");
sb.append("condition -> " + condition).append("\n");
return sb.toString();
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.test.integration.simpletons;
/**
* @author Mark Paluch
*/
public enum BookCondition {
NEW, USED;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -34,6 +34,7 @@ import org.springframework.cassandra.core.RetryPolicy;
import org.springframework.cassandra.core.WriteOptions;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.test.integration.simpletons.Book;
import org.springframework.data.cassandra.test.integration.simpletons.BookCondition;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
import org.springframework.test.context.ContextConfiguration;
@@ -46,6 +47,7 @@ import com.datastax.driver.core.querybuilder.Select;
* Unit Tests for CqlTemplate
*
* @author David Webb
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@@ -75,6 +77,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b1.setPages(521);
b1.setSaleDate(new Date());
b1.setInStock(true);
b1.setCondition(BookCondition.NEW);
template.insert(b1);
@@ -83,6 +86,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b2.setTitle("Spring Data Cassandra Guide");
b2.setAuthor("Cassandra Guru");
b2.setPages(521);
b2.setCondition(BookCondition.NEW);
template.insert(b2);
@@ -91,6 +95,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b3.setTitle("Spring Data Cassandra Guide");
b3.setAuthor("Cassandra Guru");
b3.setPages(265);
b3.setCondition(BookCondition.USED);
WriteOptions options = new WriteOptions();
options.setTtl(60);
@@ -104,6 +109,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b5.setTitle("Spring Data Cassandra Guide");
b5.setAuthor("Cassandra Guru");
b5.setPages(265);
b5.setCondition(BookCondition.USED);
template.insert(b5, options);
@@ -117,6 +123,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b1.setTitle("Spring Data Cassandra Guide");
b1.setAuthor("Cassandra Guru");
b1.setPages(521);
b1.setCondition(BookCondition.NEW);
template.insertAsynchronously(b1);
@@ -125,6 +132,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b2.setTitle("Spring Data Cassandra Guide");
b2.setAuthor("Cassandra Guru");
b2.setPages(521);
b2.setCondition(BookCondition.NEW);
template.insertAsynchronously(b2);
@@ -136,6 +144,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b3.setTitle("Spring Data Cassandra Guide");
b3.setAuthor("Cassandra Guru");
b3.setPages(265);
b3.setCondition(BookCondition.USED);
WriteOptions options = new WriteOptions();
options.setTtl(60);
@@ -152,6 +161,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b4.setTitle("Spring Data Cassandra Guide");
b4.setAuthor("Cassandra Guru");
b4.setPages(465);
b4.setCondition(BookCondition.USED);
/*
* Test Single Insert with entity
@@ -161,6 +171,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b5.setTitle("Spring Data Cassandra Guide");
b5.setAuthor("Cassandra Guru");
b5.setPages(265);
b5.setCondition(BookCondition.USED);
template.insertAsynchronously(b5, options);
@@ -251,6 +262,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
b.setPages(i * 10 + 5);
b.setInStock(true);
b.setSaleDate(new Date());
b.setCondition(BookCondition.NEW);
books.add(b);
}
@@ -659,6 +671,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa
for (Book b : bookz) {
Assert.assertTrue(b.isInStock());
Assert.assertEquals(BookCondition.NEW, b.getCondition());
}
}