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 151e22190..232fe0ad0 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 @@ -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() { @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 diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypeHolder.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypeHolder.java index 30a55ab3f..22bd2efd0 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypeHolder.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypeHolder.java @@ -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); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/convert/MappingCassandraConverterTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/convert/MappingCassandraConverterTests.java new file mode 100644 index 000000000..d567d0ce7 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/convert/MappingCassandraConverterTests.java @@ -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 getValues(Insert statement) { + return (List) ReflectionTestUtils.getField(statement, "values"); + } + + @SuppressWarnings("unchecked") + private List getAssignmentValues(Update statement) { + + List result = new ArrayList(); + + Assignments assignments = (Assignments) ReflectionTestUtils.getField(statement, "assignments"); + List listOfAssignments = (List) ReflectionTestUtils.getField(assignments, "assignments"); + for (Assignment assignment : listOfAssignments) { + result.add(ReflectionTestUtils.getField(assignment, "value")); + } + + return result; + } + + private List getWhereValues(Update statement) { + return getWhereValues(statement.where()); + } + + private List getWhereValues(BuiltStatement where) { + + List result = new ArrayList(); + + List clauses = (List) 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; + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/Book.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/Book.java index f57418631..58502e17b 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/Book.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/Book.java @@ -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(); } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/BookCondition.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/BookCondition.java new file mode 100644 index 000000000..0de4bb85b --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/BookCondition.java @@ -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; +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java index 974b886f7..ba08a308f 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java @@ -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()); } }