diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java index f3cc4c518..1e7494b16 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java @@ -234,8 +234,8 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia (CreateKeyspaceSpecification) spec).toCql() : new DropKeyspaceCqlGenerator( (DropKeyspaceSpecification) spec).toCql(); - if (log.isInfoEnabled()) { - log.info("executing CQL [{}]", cql); + if (log.isDebugEnabled()) { + log.debug("executing CQL [{}]", cql); } template.execute(cql); @@ -254,8 +254,8 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia for (String script : scripts) { - if (log.isInfoEnabled()) { - log.info("executing raw CQL [{}]", script); + if (log.isDebugEnabled()) { + log.debug("executing raw CQL [{}]", script); } template.execute(script); @@ -366,7 +366,6 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia * @param keyspaceSpecifications The keyspaceSpecifications to set. */ public void setKeyspaceSpecifications(Set> keyspaceSpecifications) { - log.info("Setter Called"); this.keyspaceSpecifications = keyspaceSpecifications; } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelSetFlattenerFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelSetFlattenerFactoryBean.java index cd76e8e13..012adeec8 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelSetFlattenerFactoryBean.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelSetFlattenerFactoryBean.java @@ -41,8 +41,8 @@ public class MultiLevelSetFlattenerFactoryBean implements FactoryBean> for (Set topSet : multiLevelSet) { for (T t : topSet) { - log.info(t.toString()); - log.info("Set contains -> " + set.contains(t)); + log.debug(t.toString()); + log.debug("Set contains -> " + set.contains(t)); set.add(t); } } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlIdentifier.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlIdentifier.java index e359c827c..a4fb3df47 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlIdentifier.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlIdentifier.java @@ -1,5 +1,6 @@ package org.springframework.cassandra.core.cql; +import java.io.Serializable; import java.util.regex.Pattern; import org.springframework.cassandra.core.ReservedKeyword; @@ -24,7 +25,9 @@ import com.datastax.driver.core.TableMetadata; * @author John McPeek * @author Matthew T. Adams */ -public final class CqlIdentifier implements Comparable { +public final class CqlIdentifier implements Comparable, Serializable { + + private static final long serialVersionUID = -974441606330912437L; public static final String UNQUOTED_REGEX = "(?i)[a-z][\\w]*"; public static final Pattern UNQUOTED = Pattern.compile(UNQUOTED_REGEX); diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java index c76d79618..40ea19a6c 100644 --- a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java @@ -16,6 +16,7 @@ package org.springframework.cassandra.test.integration; import java.io.IOException; +import java.util.UUID; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.thrift.transport.TTransportException; @@ -36,6 +37,10 @@ import com.datastax.driver.core.Session; */ public class AbstractEmbeddedCassandraIntegrationTest { + public static String uuid() { + return UUID.randomUUID().toString(); + } + static Logger log = LoggerFactory.getLogger(AbstractEmbeddedCassandraIntegrationTest.class); protected static String CASSANDRA_CONFIG = "spring-cassandra.yaml"; diff --git a/spring-cassandra/src/test/resources/logback-test.xml b/spring-cassandra/src/test/resources/logback-test.xml index 24e8a279d..453120668 100644 --- a/spring-cassandra/src/test/resources/logback-test.xml +++ b/spring-cassandra/src/test/resources/logback-test.xml @@ -10,7 +10,7 @@ - + diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java index 71fc1421b..bd6739df0 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java @@ -31,4 +31,6 @@ public interface CassandraConverter extends @Override CassandraMappingContext getMappingContext(); + + Object getId(Object object, CassandraPersistentEntity entity); } 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 9da230a43..682d66f8c 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 @@ -15,6 +15,11 @@ */ package org.springframework.data.cassandra.convert; +import static org.springframework.data.cassandra.repository.support.BasicMapId.id; + +import java.io.Serializable; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -25,6 +30,8 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.repository.MapId; +import org.springframework.data.cassandra.repository.MapIdentifiable; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.MappingContext; @@ -275,7 +282,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter implem } if (value != null) { - if (prop.isIdProperty() || entity.isCompositePrimaryKey()) { + if (prop.isIdProperty() || entity.isCompositePrimaryKey() || prop.isPrimaryKeyColumn()) { update.where(QueryBuilder.eq(prop.getColumnName().toCql(), value)); } else { update.with(QueryBuilder.set(prop.getColumnName().toCql(), value)); @@ -293,23 +300,73 @@ public class MappingCassandraConverter extends AbstractCassandraConverter implem protected void writeDeleteWhereFromWrapper(final BeanWrapper, Object> wrapper, final Where where, CassandraPersistentEntity entity) { - CassandraPersistentProperty idProperty = entity.getIdProperty(); - Object idValue = wrapper.getProperty(idProperty, idProperty.getType(), useFieldAccessOnly); - - if (idValue == null) { + Object id = getId(wrapper, entity); + if (id == null) { String msg = String.format("no id value found in object {}", wrapper.getBean()); log.error(msg); throw new IllegalArgumentException(msg); } - if (idProperty.isCompositePrimaryKey()) { - writeDeleteWhereFromWrapper( - BeanWrapper., Object> create(idValue, conversionService), where, - idProperty.getCompositePrimaryKeyEntity()); + if (id instanceof MapId) { + + for (Map.Entry entry : ((MapId) id).entrySet()) { + where.and(QueryBuilder.eq(entry.getKey(), entry.getValue())); + } return; } - where.and(QueryBuilder.eq(idProperty.getColumnName().toCql(), idValue)); + CassandraPersistentProperty idProperty = entity.getIdProperty(); + if (idProperty != null) { + + if (idProperty.isCompositePrimaryKey()) { + writeDeleteWhereFromWrapper( + BeanWrapper., Object> create(id, conversionService), where, + idProperty.getCompositePrimaryKeyEntity()); + return; + } + + where.and(QueryBuilder.eq(idProperty.getColumnName().toCql(), id)); + return; + } + } + + @Override + public Object getId(Object object, CassandraPersistentEntity entity) { + + Assert.notNull(object); + + final BeanWrapper wrapper = (object instanceof BeanWrapper) ? (BeanWrapper) object : BeanWrapper + .create(object, conversionService); + object = wrapper == null ? object : wrapper.getBean(); + + if (!entity.getType().isAssignableFrom(object.getClass())) { + throw new IllegalArgumentException(String.format( + "given instance of type [%s] is not of compatible expected type [%s]", object.getClass().getName(), entity + .getType().getName())); + } + + if (object instanceof MapIdentifiable) { + return ((MapIdentifiable) object).getMapId(); + } + + CassandraPersistentProperty idProperty = entity.getIdProperty(); + if (idProperty != null) { + return wrapper.getProperty(entity.getIdProperty(), idProperty.getType(), useFieldAccessOnly); + } + + // if the class doesn't have an id property, then it's using MapId + final MapId id = id(); + entity.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(CassandraPersistentProperty p) { + if (p.isPrimaryKeyColumn()) { + id.with(p.getName(), (Serializable) wrapper.getProperty(p, p.getType(), useFieldAccessOnly)); + } + } + }); + + return id; } @SuppressWarnings("unchecked") diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java index 21cd70616..4d4d43280 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java @@ -18,6 +18,7 @@ package org.springframework.data.cassandra.core; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.springframework.cassandra.core.CqlTemplate; import org.springframework.cassandra.core.QueryOptions; @@ -288,8 +289,24 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation void doWithClause(Clause clause); } + protected void appendIdCriteria(final ClauseCallback clauseCallback, CassandraPersistentEntity entity, + final Map id) { + + for (Map.Entry entry : id.entrySet()) { + + CassandraPersistentProperty property = entity.getPersistentProperty(entry.getKey().toString()); + clauseCallback.doWithClause(QueryBuilder.eq(property.getColumnName().toCql(), entry.getValue())); + } + } + protected void appendIdCriteria(final ClauseCallback clauseCallback, CassandraPersistentEntity entity, Object id) { + if (id instanceof Map) { + + appendIdCriteria(clauseCallback, entity, (Map) id); + return; + } + CassandraPersistentProperty idProperty = entity.getIdProperty(); if (idProperty.isCompositePrimaryKey()) { @@ -418,7 +435,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation */ protected T selectOne(String query, CassandraConverterRowCallback readRowCallback) { - logger.info(query); + logger.debug(query); ResultSet resultSet = query(query); @@ -448,9 +465,8 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Batch b = createDeleteBatchQuery(getTableName(entities.get(0).getClass()).toCql(), entities, options, cassandraConverter); - logger.info(b.toString()); - String query = b.getQueryString(); + logger.debug(query); if (asynchronously) { executeAsynchronously(query); @@ -484,7 +500,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation cassandraConverter); String query = b.getQueryString(); - logger.info(query); + logger.debug(query); if (asychronously) { executeAsynchronously(query); @@ -512,7 +528,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation cassandraConverter); String query = b.getQueryString(); - logger.info(query); + logger.debug(query); if (asychronously) { executeAsynchronously(query); @@ -534,9 +550,9 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entity); Delete delete = createDeleteQuery(getTableName(entity.getClass()).toCql(), entity, options, cassandraConverter); - logger.info(delete.toString()); String query = delete.getQueryString(); + logger.debug(query); if (asynchronously) { executeAsynchronously(query); @@ -561,7 +577,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Update q = toUpdateQuery(getTableName(entity.getClass()).toCql(), entity, options, cassandraConverter); String query = q.getQueryString(); - logger.info(query); + logger.debug(query); if (asychronously) { executeAsynchronously(query); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java index 14fe1d867..a306a689f 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java @@ -187,8 +187,10 @@ public class DefaultCassandraMappingContext extends } else { - if (prop.isIdProperty()) { + if (prop.isIdProperty() || prop.isPartitionKeyColumn()) { spec.partitionKeyColumn(prop.getColumnName(), prop.getDataType()); + } else if (prop.isClusterKeyColumn()) { + spec.clusteredKeyColumn(prop.getColumnName(), prop.getDataType()); } else { spec.column(prop.getColumnName(), prop.getDataType()); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPersistentEntityMetadataVerifier.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPersistentEntityMetadataVerifier.java index 5f7dcabc0..5861b4d84 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPersistentEntityMetadataVerifier.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPersistentEntityMetadataVerifier.java @@ -22,7 +22,6 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.cassandra.core.PrimaryKeyType; import org.springframework.data.annotation.Persistent; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.MappingException; @@ -48,6 +47,8 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand final List idProperties = new ArrayList(); final List compositePrimaryKeys = new ArrayList(); + final List partitionKeyColumns = new ArrayList(); + final List clusterKeyColumns = new ArrayList(); final List primaryKeyColumns = new ArrayList(); /* @@ -87,13 +88,20 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand idProperties.add(p); } else if (p.isCompositePrimaryKey()) { compositePrimaryKeys.add(p); - } else if (p.isPrimaryKeyColumn()) { + } else if (p.isPartitionKeyColumn()) { + partitionKeyColumns.add(p); + primaryKeyColumns.add(p); + } else if (p.isClusterKeyColumn()) { + clusterKeyColumns.add(p); primaryKeyColumns.add(p); } - } }); + final int idPropertyCount = idProperties.size(); + final int partitionKeyColumnCount = partitionKeyColumns.size(); + final int primaryKeyColumnCount = primaryKeyColumns.size(); + /* * Perform rules verification on PrimaryKeyClass */ @@ -102,7 +110,7 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand /* * Must have at least 1 attribute annotated with @PrimaryKeyColumn */ - if (primaryKeyColumns.size() == 0) { + if (primaryKeyColumnCount == 0) { exceptions.add(new MappingException(String.format( "composite primary key type [%s] has no fields annotated with @%s", entity.getType().getName(), PrimaryKeyColumn.class.getSimpleName()))); @@ -111,22 +119,15 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand /* * At least one of the PrimaryKeyColumns must have a type PARTIONED */ - boolean partitionKeyExists = false; - for (CassandraPersistentProperty p : primaryKeyColumns) { - if (p.getField().getAnnotation(PrimaryKeyColumn.class).type() == PrimaryKeyType.PARTITIONED) { - partitionKeyExists = true; - break; - } - } - if (!partitionKeyExists) { + if (partitionKeyColumnCount == 0) { exceptions.add(new MappingException( - "At least on of the PrimaryKeyColumn annotation must have a type of PARTITIONED")); + "At least one of the @PrimaryKeyColumn annotation must have a type of PARTITIONED")); } /* * Cannot have any Id or PrimaryKey Annotations */ - if (idProperties.size() > 0) { + if (idPropertyCount > 0) { exceptions.add(new MappingException( "Annotations @Id and @PrimaryKey are invalid for type annotated with @PrimaryKeyClass")); } @@ -155,7 +156,7 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand } /* - * Ensure PrimaryKeyClass overrides "boolean equals(Object)" + * Check that PrimaryKeyClass overrides "boolean equals(Object)" */ try { Method equalsMethod = thisType.getDeclaredMethod("equals", Object.class); @@ -163,7 +164,7 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand throw new NoSuchMethodException(); } } catch (NoSuchMethodException e) { - String message = "@PrimaryKeyClass must override 'boolean equals(Object)' method and use all @PrimaryKeyColumn fields"; + String message = "@PrimaryKeyClass should override 'boolean equals(Object)' method and use all @PrimaryKeyColumn fields"; if (strict) { exceptions.add(new MappingException(message, e)); } else { @@ -180,14 +181,13 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand throw new NoSuchMethodException(); } } catch (NoSuchMethodException e) { - String message = "@PrimaryKeyClass must override 'int hashCode()' method and use all @PrimaryKeyColumn fields"; + String message = "@PrimaryKeyClass should override 'int hashCode()' method and use all @PrimaryKeyColumn fields"; if (strict) { exceptions.add(new MappingException(message, e)); } else { log.warn(message); } } - } /* @@ -200,23 +200,48 @@ public class DefaultCassandraPersistentEntityMetadataVerifier implements Cassand */ /* - * Ensure only one PK + * Ensure only one PK or at least one partitioned PKC and not both PK(s) & PKC(s) */ - int idPropertyCount = idProperties.size(); - if (idPropertyCount != 1) { - exceptions.add(new MappingException(String.format( - "@Table/@Persistent types must have only one @PrimaryKey attribute. Found %s.", idPropertyCount))); + if (primaryKeyColumnCount == 0) { + /* + * Can only have one PK. + */ + if (idPropertyCount != 1) { + exceptions + .add(new MappingException(String.format( + "@Table/@Persistent types must have only one @PrimaryKey attribute, if any. Found %s.", + idPropertyCount))); + throw exceptions; + } + /* + * Ensure that Id is a supported Type. At the point there is only 1. + */ + Class typeClass = idProperties.get(0).getType(); + if (!typeClass.isAnnotationPresent(PrimaryKeyClass.class) + && CassandraSimpleTypeHolder.getDataTypeFor(typeClass) == null) { + exceptions.add(new MappingException( + "Fields annotated with @PrimaryKey must be simple CassandraTypes or @PrimaryKeyClass type")); + } + } else if (idPropertyCount > 0) { + /* + * Then we have both PK(s) & PKC(s) + */ + exceptions + .add(new MappingException( + String + .format( + "@Table/@Persistent types must not define both @PrimaryKeyColumn field%s (found %s) and @PrimaryKey field%s (found %s)", + primaryKeyColumnCount == 1 ? "" : "s", primaryKeyColumnCount, idPropertyCount == 1 ? "" : "s", + idPropertyCount))); throw exceptions; - } - - /* - * Ensure that Id is a supported Type. At the point there is only 1. - */ - Class typeClass = idProperties.get(0).getType(); - if (!typeClass.isAnnotationPresent(PrimaryKeyClass.class) - && CassandraSimpleTypeHolder.getDataTypeFor(typeClass) == null) { - exceptions.add(new MappingException( - "Fields annotated with @PrimaryKey must be simple CassandraTypes or @PrimaryKeyClass type")); + } else { + /* + * We have no PKs & only PKC(s) -- ensure at least one is of type PARTITIONED + */ + if (partitionKeyColumnCount == 0) { + exceptions.add(new MappingException(String + .format("@Table/@Persistent types must define at least one @PrimaryKeyColumn of type PARTITIONED"))); + } } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java index 1d525745f..400cdad4b 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/CassandraRepository.java @@ -1,29 +1,38 @@ -/* - * Copyright 2013-2014 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.repository; import java.io.Serializable; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.cassandra.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.repository.support.BasicMapId; /** - * Cassandra-specific extension of the {@link CrudRepository} interface. + * Basic Cassandra repository interface. + *

+ * This interface uses {@link MapId} for the id type, allowing you to annotate entity fields or properties with + * {@link PrimaryKeyColumn @PrimaryKeyColumn}. For a full discussion of this interface, including the use of custom + * primary key classes, see {@link TypedIdCassandraRepository}. + *

+ * Steps to use this interface: + *

    + *
  • Define your entity, including a field or property for each column, including those for partition and (optional) + * cluster columns.
  • + *
  • Annotate each partition & cluster field or property with {@link PrimaryKeyColumn @PrimaryKeyColumn}
  • + *
  • Define your repository interface to be a subinterface of this interface, which uses a provided id type, + * {@link MapId} (implemented by {@link BasicMapId}).
  • + *
  • Whenever you need a {@link MapId}, you can use the static factory method {@link BasicMapId#id()} (which is + * convenient if you import statically) and the builder method {@link MapId#with(String, Serializable)} to easily + * construct an id.
  • + *
  • Optionally, entity class authors can have their entities implement {@link MapIdentifiable}, to make it easier and + * quicker for entity clients to get the entity's identity.
  • + *
+ * + * @param The type of the persistent entity. + * + * @see TypedIdCassandraRepository + * @see MapId + * @See {@link MapIdentifiable} * - * @author Alex Shvid * @author Matthew T. Adams */ -public interface CassandraRepository extends CrudRepository { +public interface CassandraRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/MapId.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/MapId.java new file mode 100644 index 000000000..d323ee092 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/MapId.java @@ -0,0 +1,22 @@ +package org.springframework.data.cassandra.repository; + +import java.io.Serializable; +import java.util.Map; + +/** + * Interface that represents the id of a persistent entity, where the keys correspond to the entity's JavaBean + * properties. + * + * @author Matthew T. Adams + */ +public interface MapId extends Serializable, Map { + + /** + * Builder method that adds the value for the named property, then returns this. + * + * @param name The property name containing the value. + * @param value The property value. + * @return this + */ + MapId with(String name, Serializable value); +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/MapIdentifiable.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/MapIdentifiable.java new file mode 100644 index 000000000..511fa88a5 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/MapIdentifiable.java @@ -0,0 +1,16 @@ +package org.springframework.data.cassandra.repository; + +/** + * Interface that entity classes may choose to implement in order to allow a client of the entity to easily get the + * entity's {@link MapId}. + * + * @author Matthew T. Adams + */ +public interface MapIdentifiable { + + /** + * Gets the identity of this instance. Throws {@link IllegalStateException} if this instance does not use + * {@link MapId}. + */ + MapId getMapId(); +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/TypedIdCassandraRepository.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/TypedIdCassandraRepository.java new file mode 100644 index 000000000..fc84d5d0f --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/TypedIdCassandraRepository.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2014 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.repository; + +import java.io.Serializable; + +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.data.cassandra.repository.support.BasicMapId; +import org.springframework.data.domain.Persistable; +import org.springframework.data.repository.CrudRepository; + +/** + * Cassandra-specific extension of the {@link CrudRepository} interface that allows the specification of a type for the + * identity of the {@link Table @Table} (or {@link Persistable @Persistable}) type. + *

+ * If a single column comprises the identity of the entity, then you must do one of two things: + *

    + *
  • annotate the field or property in your entity with {@link PrimaryKey @PrimaryKey} and declare your repository + * interface to be a subinterface of this interface, specifying the entity type and id type, or
  • + *
  • annotate the field or property in your entity with {@link PrimaryKeyColumn @PrimaryKeyColumn} and declare your + * repository interface to be a subinterface of {@link CassandraRepository}.
  • + *
+ * If multiple columns comprise the identity of the entity, then you must employ one of the following two strategies. + *
    + *
  • Strategy: use an explicit primary key class + *
      + *
    • Define a primary key class (annotated with {@link PrimaryKeyClass @PrimaryKeyClass}) that represents your + * entity's identity.
    • + *
    • Define your entity to include a field or property of the type of your primary key class, and annotate that field + * with {@link PrimaryKey @PrimaryKey}.
    • + *
    • Define your repository interface to be a subinterface of this interface, including your entity type and + * your primary key class type.
    • + *
    + *
  • + * Strategy: embed identity fields or properties directly in your entity and use {@link CassandraRepository}
  • + *
      + *
    • Define your entity, including a field or property for each column, including those for partition and (optional) + * cluster columns.
    • + *
    • Annotate each partition & cluster field or property with {@link PrimaryKeyColumn @PrimaryKeyColumn}
    • + *
    • Define your repository interface to be a subinterface of {@link CassandraRepository}, which uses a provided id + * type, {@link MapId} (implemented by {@link BasicMapId}).
    • + *
    • Whenever you need a {@link MapId}, you can use the static factory method {@link BasicMapId#id()} (which is + * convenient if you import statically) and the builder method {@link MapId#with(String, Serializable)} to easily + * construct an id.
    • + *
    + *
+ * + * @author Alex Shvid + * @author Matthew T. Adams + */ +public interface TypedIdCassandraRepository extends CrudRepository { +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java index 123d5ba99..b7c8976fe 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityInformation.java @@ -17,7 +17,6 @@ package org.springframework.data.cassandra.repository.query; import java.io.Serializable; -import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.data.repository.core.EntityInformation; /** @@ -26,12 +25,7 @@ import org.springframework.data.repository.core.EntityInformation; * @author Alex Shvid * */ -public interface CassandraEntityInformation extends EntityInformation { +public interface CassandraEntityInformation extends EntityInformation, + CassandraEntityMetadata { - /** - * Returns the name of the table the entity shall be persisted to. - * - * @return - */ - CqlIdentifier getTableName(); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityMetadata.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityMetadata.java index f71ca93ea..9d4873b8a 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityMetadata.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraEntityMetadata.java @@ -15,12 +15,14 @@ */ package org.springframework.data.cassandra.repository.query; +import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.data.repository.core.EntityMetadata; /** * Extension of {@link EntityMetadata} to additionally expose the table name an entity shall be persisted to. * * @author Alex Shvid + * @author Matthew T. Adams * * @param */ @@ -31,5 +33,5 @@ public interface CassandraEntityMetadata extends EntityMetadata { * * @return */ - String getTableName(); + CqlIdentifier getTableName(); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/BasicMapId.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/BasicMapId.java new file mode 100644 index 000000000..b48b60ac4 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/BasicMapId.java @@ -0,0 +1,168 @@ +package org.springframework.data.cassandra.repository.support; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.repository.MapId; +import org.springframework.util.Assert; + +/** + * Simple implementation of {@link MapId}. + *

+ * Note: This could be extended in various cool ways, like one that takes a type and validates that the given + * name corresponds to an actual field or bean property on that type. There could also be another one that uses a + * {@link CassandraPersistentEntity} and {@link CassandraPersistentProperty} instead of a String name. + * + * @author Matthew T. Adams + */ +@SuppressWarnings("serial") +public class BasicMapId implements MapId { + + /** + * Factory method. Convenient if imported statically. + * + * @return {@link BasicMapId} + */ + public static MapId id() { + return new BasicMapId(); + } + + /** + * Factory method. Convenient if imported statically. + * + * @return {@link BasicMapId} + */ + public static MapId id(String name, Serializable value) { + return new BasicMapId().with(name, value); + } + + /** + * Factory method. Convenient if imported statically. + * + * @return {@link BasicMapId} + */ + public static MapId id(MapId id) { + return new BasicMapId(id); + } + + protected Map map = new HashMap(); + + public BasicMapId() { + } + + public BasicMapId(Map map) { + + Assert.notNull(map); + map.putAll(map); + } + + @Override + public BasicMapId with(String name, Serializable value) { + put(name, value); + return this; + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public boolean containsKey(Object name) { + return map.containsKey(name); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null) { + return false; + } + if (!(that instanceof Map)) { // we can be equal to a Map + return false; + } + return map.equals(that); + } + + @Override + public Serializable get(Object name) { + return map.get(name); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Serializable put(String name, Serializable value) { + return map.put(name, value); + } + + @Override + public void putAll(Map source) { + map.putAll(source); + } + + @Override + public Serializable remove(Object name) { + return map.remove(name); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public String toString() { + + StringBuilder s = new StringBuilder("{ "); + + boolean first = true; + for (Map.Entry entry : map.entrySet()) { + + if (first) { + first = false; + } else { + s.append(", "); + } + + s.append(entry.getKey()).append(" : ").append(entry.getValue()); + } + + return s.append(" }").toString(); + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java index 0a3007a83..fc03dcfce 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java @@ -20,7 +20,7 @@ import java.io.Serializable; import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; import org.springframework.data.cassandra.repository.query.CassandraEntityInformation; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.MappingException; @@ -29,10 +29,10 @@ import org.springframework.data.repository.core.support.RepositoryFactorySupport import org.springframework.util.Assert; /** - * Factory to create {@link CassandraRepository} instances. + * Factory to create {@link TypedIdCassandraRepository} instances. * * @author Alex Shvid - * + * @author Matthew T. Adams */ public class CassandraRepositoryFactory extends RepositoryFactorySupport { @@ -68,10 +68,6 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport { } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class) - */ @Override @SuppressWarnings("unchecked") public CassandraEntityInformation getEntityInformation(Class domainClass) { @@ -83,6 +79,7 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport { domainClass.getName())); } - return new MappingCassandraEntityInformation((CassandraPersistentEntity) entity); + return new MappingCassandraEntityInformation((CassandraPersistentEntity) entity, + cassandraTemplate.getConverter()); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryBean.java index a75d1b8f1..3ea8fbbc3 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryBean.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryBean.java @@ -18,14 +18,14 @@ package org.springframework.data.cassandra.repository.support; import java.io.Serializable; import org.springframework.data.cassandra.core.CassandraTemplate; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.util.Assert; /** - * {@link org.springframework.beans.factory.FactoryBean} to create {@link CassandraRepository} instances. + * {@link org.springframework.beans.factory.FactoryBean} to create {@link TypedIdCassandraRepository} instances. * * @author Alex Shvid * diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java index 25b333b08..63e84493b 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MappingCassandraEntityInformation.java @@ -18,8 +18,10 @@ package org.springframework.data.cassandra.repository.support; import java.io.Serializable; import org.springframework.cassandra.core.cql.CqlIdentifier; +import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.repository.MapId; import org.springframework.data.cassandra.repository.query.CassandraEntityInformation; import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.repository.core.support.AbstractEntityInformation; @@ -31,34 +33,25 @@ import org.springframework.util.Assert; * the {@link CassandraPersistentEntity} if given. * * @author Alex Shvid - * + * @author Matthew T. Adams */ public class MappingCassandraEntityInformation extends AbstractEntityInformation implements CassandraEntityInformation { private final CassandraPersistentEntity entityMetadata; - private final CqlIdentifier customTableName; + private CassandraConverter converter; /** * Creates a new {@link MappingCassandraEntityInformation} for the given {@link CassandraPersistentEntity}. * * @param entity must not be {@literal null}. */ - public MappingCassandraEntityInformation(CassandraPersistentEntity entity) { - this(entity, null); - } + public MappingCassandraEntityInformation(CassandraPersistentEntity entity, CassandraConverter converter) { - /** - * Creates a new {@link MappingCassandraEntityInformation} for the given {@link CassandraPersistentEntity} and custom - * table name. - * - * @param entity must not be {@literal null}. - * @param customTableName - */ - public MappingCassandraEntityInformation(CassandraPersistentEntity entity, CqlIdentifier customTableName) { super(entity.getType()); + this.entityMetadata = entity; - this.customTableName = customTableName; + this.converter = converter; } @SuppressWarnings("unchecked") @@ -68,21 +61,21 @@ public class MappingCassandraEntityInformation exten Assert.notNull(entity); CassandraPersistentProperty idProperty = entityMetadata.getIdProperty(); - if (idProperty == null) { - return null; + if (idProperty != null) { + return (ID) BeanWrapper.create(entity, null).getProperty(idProperty); } - return (ID) BeanWrapper.create(entity, null).getProperty(idProperty); + return (ID) converter.getId(entity, entityMetadata); } @SuppressWarnings("unchecked") @Override public Class getIdType() { - return (Class) entityMetadata.getIdProperty().getType(); + return (Class) (entityMetadata.getIdProperty() == null ? MapId.class : entityMetadata.getIdProperty().getType()); } @Override public CqlIdentifier getTableName() { - return customTableName == null ? entityMetadata.getTableName() : customTableName; + return entityMetadata.getTableName(); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java index 39163244f..8ead5a34c 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java @@ -21,7 +21,7 @@ import java.util.List; import org.springframework.cassandra.core.util.CollectionUtils; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.CassandraTemplate; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; import org.springframework.data.cassandra.repository.query.CassandraEntityInformation; import org.springframework.util.Assert; @@ -33,7 +33,7 @@ import com.datastax.driver.core.querybuilder.Select; * @author Alex Shvid * @author Matthew T. Adams */ -public class SimpleCassandraRepository implements CassandraRepository { +public class SimpleCassandraRepository implements TypedIdCassandraRepository { protected CassandraOperations template; protected CassandraEntityInformation entityInformation; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/collections/CollectionsRowValueProviderTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/collections/CollectionsRowValueProviderTest.java index b7d3a7b95..92e7dfad1 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/collections/CollectionsRowValueProviderTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/collections/CollectionsRowValueProviderTest.java @@ -89,9 +89,9 @@ public class CollectionsRowValueProviderTest extends AbstractSpringDataEmbeddedC Assert.assertNotNull(b.getCheckOuts()); - log.info("Checkouts map data"); + log.debug("Checkouts map data"); for (String username : b.getCheckOuts().keySet()) { - log.info(username + " has " + b.getCheckOuts().get(username) + " checkouts of this book."); + log.debug(username + " has " + b.getCheckOuts().get(username) + " checkouts of this book."); } Assert.assertEquals(b.getTitle(), "Spring Data Cassandra Guide"); @@ -132,14 +132,14 @@ public class CollectionsRowValueProviderTest extends AbstractSpringDataEmbeddedC Assert.assertNotNull(b.getReferences()); Assert.assertNotNull(b.getBookmarks()); - log.info("Bookmark List Data"); + log.debug("Bookmark List Data"); for (Integer mark : b.getBookmarks()) { - log.info("Bookmark set on page " + mark); + log.debug("Bookmark set on page " + mark); } - log.info("Reference Set Data"); + log.debug("Reference Set Data"); for (String ref : b.getReferences()) { - log.info("Reference -> " + ref); + log.debug("Reference -> " + ref); } Assert.assertEquals(b.getTitle(), "Spring Data Cassandra Guide"); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepository.java index 1f1dca7a1..32e1176ca 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepository.java @@ -15,7 +15,7 @@ */ package org.springframework.data.cassandra.test.integration.composites; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface CommentRepository extends CassandraRepository { +public interface CommentRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ExplicitRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ExplicitRepository.java index d825070b2..ddae393dd 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ExplicitRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ExplicitRepository.java @@ -1,6 +1,6 @@ package org.springframework.data.cassandra.test.integration.forcequote.compositeprimarykey; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface ExplicitRepository extends CassandraRepository { +public interface ExplicitRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ImplicitRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ImplicitRepository.java index 7bc2d1277..644029a52 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ImplicitRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ImplicitRepository.java @@ -1,6 +1,6 @@ package org.springframework.data.cassandra.test.integration.forcequote.compositeprimarykey; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface ImplicitRepository extends CassandraRepository { +public interface ImplicitRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitPropertiesRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitPropertiesRepository.java index bf604f4b4..a72a59e58 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitPropertiesRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitPropertiesRepository.java @@ -1,6 +1,6 @@ package org.springframework.data.cassandra.test.integration.forcequote.config; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface ExplicitPropertiesRepository extends CassandraRepository { +public interface ExplicitPropertiesRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitRepository.java index 3dec3a438..83f5ccb4d 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ExplicitRepository.java @@ -1,6 +1,6 @@ package org.springframework.data.cassandra.test.integration.forcequote.config; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface ExplicitRepository extends CassandraRepository { +public interface ExplicitRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitPropertiesRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitPropertiesRepository.java index 875c897d0..8e51a83c8 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitPropertiesRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitPropertiesRepository.java @@ -1,6 +1,6 @@ package org.springframework.data.cassandra.test.integration.forcequote.config; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface ImplicitPropertiesRepository extends CassandraRepository { +public interface ImplicitPropertiesRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitRepository.java index 606f3fea7..944d571b2 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ImplicitRepository.java @@ -1,6 +1,6 @@ package org.springframework.data.cassandra.test.integration.forcequote.config; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface ImplicitRepository extends CassandraRepository { +public interface ImplicitRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityVerifierIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityVerifierIntegrationTest.java index aa39100fb..83137a472 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityVerifierIntegrationTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityVerifierIntegrationTest.java @@ -15,6 +15,8 @@ */ package org.springframework.data.cassandra.test.integration.mapping; +import static org.junit.Assert.*; + import java.io.Serializable; import org.junit.Before; @@ -23,6 +25,7 @@ import org.springframework.cassandra.core.Ordering; import org.springframework.cassandra.core.PrimaryKeyType; import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.DefaultCassandraMappingContext; import org.springframework.data.cassandra.mapping.PrimaryKey; import org.springframework.data.cassandra.mapping.PrimaryKeyClass; @@ -82,6 +85,36 @@ public class BasicCassandraPersistentEntityVerifierIntegrationTest { } + @Test(expected = MappingException.class) + public void testNoPartitionKey() { + + mappingContext.getPersistentEntity(NoPartitionKey.class); + + } + + @Test(expected = MappingException.class) + public void testPkAndPkc() { + + mappingContext.getPersistentEntity(PkAndPkc.class); + + } + + @Test + public void testOnePkc() { + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(OnePkc.class); + + assertNull(entity.getIdProperty()); + } + + @Test + public void testMultiPkc() { + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(MultiPkc.class); + + assertNull(entity.getIdProperty()); + } + static class NonPersistentClass { @Id @@ -147,13 +180,44 @@ public class BasicCassandraPersistentEntityVerifierIntegrationTest { @PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING) private String color; - } @Table @PrimaryKeyClass static class TooManyAnnotations { - } + @Table + public static class NoPartitionKey { + + @PrimaryKeyColumn(ordinal = 0) + String key; + } + + @Table + public static class PkAndPkc { + + @PrimaryKey + String primaryKey; + + @PrimaryKeyColumn(ordinal = 0) + String primaryKeyColumn; + } + + @Table + public static class OnePkc { + + @PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 0) + String pk; + } + + @Table + public static class MultiPkc { + + @PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 0) + String pk0; + + @PrimaryKeyColumn(ordinal = 1) + String pk1; + } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/MultiPkc.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/MultiPkc.java new file mode 100644 index 000000000..b2c002f2c --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/MultiPkc.java @@ -0,0 +1,56 @@ +package org.springframework.data.cassandra.test.integration.mapping.mapid.repo; + +import org.springframework.cassandra.core.PrimaryKeyType; +import org.springframework.data.cassandra.mapping.Column; +import org.springframework.data.cassandra.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.mapping.Table; + +@Table +public class MultiPkc { + + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) + String key0; + + @PrimaryKeyColumn(ordinal = 1) + String key1; + + @Column + String value; + + /** + * @deprecated for persistence use only + */ + @Deprecated + @SuppressWarnings("unused") + private MultiPkc() { + } + + public MultiPkc(String key0, String key1) { + setKey0(key0); + setKey1(key1); + } + + public String getKey0() { + return key0; + } + + public void setKey0(String key0) { + this.key0 = key0; + } + + public String getKey1() { + return key1; + } + + public void setKey1(String key1) { + this.key1 = key1; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/MultiPkcRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/MultiPkcRepository.java new file mode 100644 index 000000000..c7f02b980 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/MultiPkcRepository.java @@ -0,0 +1,6 @@ +package org.springframework.data.cassandra.test.integration.mapping.mapid.repo; + +import org.springframework.data.cassandra.repository.CassandraRepository; + +public interface MultiPkcRepository extends CassandraRepository { +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/RepositoryMapIdIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/RepositoryMapIdIntegrationTest.java new file mode 100644 index 000000000..2df98426d --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/RepositoryMapIdIntegrationTest.java @@ -0,0 +1,114 @@ +package org.springframework.data.cassandra.test.integration.mapping.mapid.repo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.springframework.data.cassandra.repository.support.BasicMapId.id; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.repository.MapId; +import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories; +import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest; +import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class RepositoryMapIdIntegrationTest extends AbstractSpringDataEmbeddedCassandraIntegrationTest { + + @Configuration + @EnableCassandraRepositories(basePackageClasses = RepositoryMapIdIntegrationTest.class) + public static class Config extends IntegrationTestConfig { + + @Override + public String[] getEntityBasePackages() { + return new String[] { SinglePkc.class.getPackage().getName() }; + } + } + + @Autowired + CassandraOperations t; + + @Autowired + SinglePkcRepository sr; + + @Autowired + MultiPkcRepository mr; + + @Before + public void before() { + assertNotNull(t); + assertNotNull(sr); + assertNotNull(mr); + } + + @Test + public void testSinglePkc() { + + // insert + SinglePkc inserted = new SinglePkc(uuid()); + inserted.setValue(uuid()); + SinglePkc saved = sr.save(inserted); + assertSame(saved, inserted); + + // select + MapId id = id("key", saved.getKey()); + SinglePkc selected = sr.findOne(id); + assertNotSame(selected, saved); + assertEquals(saved.getKey(), selected.getKey()); + assertEquals(saved.getValue(), selected.getValue()); + + // update + selected.setValue(uuid()); + SinglePkc updated = sr.save(selected); + assertSame(updated, selected); + + selected = sr.findOne(id); + assertNotSame(selected, updated); + assertEquals(updated.getValue(), selected.getValue()); + + // delete + sr.delete(selected); + assertNull(sr.findOne(id)); + } + + @Test + public void testMultiPkc() { + + // insert + MultiPkc inserted = new MultiPkc(uuid(), uuid()); + inserted.setValue(uuid()); + MultiPkc saved = mr.save(inserted); + assertSame(saved, inserted); + + // select + MapId id = id("key0", saved.getKey0()).with("key1", saved.getKey1()); + MultiPkc selected = mr.findOne(id); + assertNotSame(selected, saved); + assertEquals(saved.getKey0(), selected.getKey0()); + assertEquals(saved.getKey1(), selected.getKey1()); + assertEquals(saved.getValue(), selected.getValue()); + + // update + selected.setValue(uuid()); + MultiPkc updated = mr.save(selected); + assertSame(updated, selected); + + selected = mr.findOne(id); + assertNotSame(selected, updated); + assertEquals(updated.getValue(), selected.getValue()); + + // delete + t.delete(selected); + assertNull(mr.findOne(id)); + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/SinglePkc.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/SinglePkc.java new file mode 100644 index 000000000..7e0ce4caf --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/SinglePkc.java @@ -0,0 +1,44 @@ +package org.springframework.data.cassandra.test.integration.mapping.mapid.repo; + +import org.springframework.cassandra.core.PrimaryKeyType; +import org.springframework.data.cassandra.mapping.Column; +import org.springframework.data.cassandra.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.mapping.Table; + +@Table +public class SinglePkc { + + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) + String key; + + @Column + String value; + + /** + * @deprecated for persistence use only + */ + @Deprecated + @SuppressWarnings("unused") + private SinglePkc() { + } + + public SinglePkc(String key) { + setKey(key); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/SinglePkcRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/SinglePkcRepository.java new file mode 100644 index 000000000..436f134a5 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/repo/SinglePkcRepository.java @@ -0,0 +1,6 @@ +package org.springframework.data.cassandra.test.integration.mapping.mapid.repo; + +import org.springframework.data.cassandra.repository.CassandraRepository; + +public interface SinglePkcRepository extends CassandraRepository { +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/template/CassandraTemplateMapIdIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/template/CassandraTemplateMapIdIntegrationTest.java new file mode 100644 index 000000000..c3ae31241 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/template/CassandraTemplateMapIdIntegrationTest.java @@ -0,0 +1,195 @@ +package org.springframework.data.cassandra.test.integration.mapping.mapid.template; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.springframework.data.cassandra.repository.support.BasicMapId.id; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cassandra.core.PrimaryKeyType; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.mapping.Column; +import org.springframework.data.cassandra.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.mapping.Table; +import org.springframework.data.cassandra.repository.MapId; +import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest; +import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class CassandraTemplateMapIdIntegrationTest extends AbstractSpringDataEmbeddedCassandraIntegrationTest { + + @Configuration + public static class Config extends IntegrationTestConfig { + + @Override + public String[] getEntityBasePackages() { + return new String[] { SinglePkc.class.getPackage().getName() }; + } + } + + @Autowired + CassandraOperations t; + + @Before + public void before() { + assertNotNull(t); + } + + @Test + public void testSinglePkc() { + + // insert + SinglePkc inserted = new SinglePkc(uuid()); + inserted.setValue(uuid()); + SinglePkc saved = t.insert(inserted); + assertSame(saved, inserted); + + // select + MapId id = id("key", saved.getKey()); + SinglePkc selected = t.selectOneById(SinglePkc.class, id); + assertNotSame(selected, saved); + assertEquals(saved.getKey(), selected.getKey()); + assertEquals(saved.getValue(), selected.getValue()); + + // update + selected.setValue(uuid()); + SinglePkc updated = t.update(selected); + assertSame(updated, selected); + + selected = t.selectOneById(SinglePkc.class, id); + assertNotSame(selected, updated); + assertEquals(updated.getValue(), selected.getValue()); + + // delete + t.delete(selected); + assertNull(t.selectOneById(SinglePkc.class, id)); + } + + @Table + public static class SinglePkc { + + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) + String key; + + @Column + String value; + + /** + * @deprecated for persistence use only + */ + @Deprecated + @SuppressWarnings("unused") + private SinglePkc() { + } + + public SinglePkc(String key) { + setKey(key); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + @Test + public void testMultiPkc() { + + // insert + MultiPkc inserted = new MultiPkc(uuid(), uuid()); + inserted.setValue(uuid()); + MultiPkc saved = t.insert(inserted); + assertSame(saved, inserted); + + // select + MapId id = id("key0", saved.getKey0()).with("key1", saved.getKey1()); + MultiPkc selected = t.selectOneById(MultiPkc.class, id); + assertNotSame(selected, saved); + assertEquals(saved.getKey0(), selected.getKey0()); + assertEquals(saved.getKey1(), selected.getKey1()); + assertEquals(saved.getValue(), selected.getValue()); + + // update + selected.setValue(uuid()); + MultiPkc updated = t.update(selected); + assertSame(updated, selected); + + selected = t.selectOneById(MultiPkc.class, id); + assertNotSame(selected, updated); + assertEquals(updated.getValue(), selected.getValue()); + + // delete + t.delete(selected); + assertNull(t.selectOneById(MultiPkc.class, id)); + } + + @Table + public static class MultiPkc { + + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) + String key0; + + @PrimaryKeyColumn(ordinal = 1) + String key1; + + @Column + String value; + + /** + * @deprecated for persistence use only + */ + @Deprecated + @SuppressWarnings("unused") + private MultiPkc() { + } + + public MultiPkc(String key0, String key1) { + setKey0(key0); + setKey1(key1); + } + + public String getKey0() { + return key0; + } + + public void setKey0(String key0) { + this.key0 = key0; + } + + public String getKey1() { + return key1; + } + + public void setKey1(String key1) { + this.key1 = key1; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMinRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMinRepository.java index 23bcffc61..e6fb1017d 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMinRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMinRepository.java @@ -15,7 +15,7 @@ */ package org.springframework.data.cassandra.test.integration.minimal.config.entities; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; -public interface AbsMinRepository extends CassandraRepository { +public interface AbsMinRepository extends TypedIdCassandraRepository { } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepository.java index d6f997c5b..293404960 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepository.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepository.java @@ -15,7 +15,7 @@ */ package org.springframework.data.cassandra.test.integration.repository; -import org.springframework.data.cassandra.repository.CassandraRepository; +import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; /** * Sample repository managing {@link User} entities. @@ -23,5 +23,5 @@ import org.springframework.data.cassandra.repository.CassandraRepository; * @author Alex Shvid * */ -public interface UserRepository extends CassandraRepository { +public interface UserRepository extends TypedIdCassandraRepository { } 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 9fd7bffdb..ec2eefcb2 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 @@ -606,8 +606,8 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa Book b = template.selectOne(select.getQueryString(), Book.class); - log.info("SingleSelect Book Title -> " + b.getTitle()); - log.info("SingleSelect Book Author -> " + b.getAuthor()); + log.debug("SingleSelect Book Title -> " + b.getTitle()); + log.debug("SingleSelect Book Author -> " + b.getAuthor()); Assert.assertEquals(b.getTitle(), "Spring Data Cassandra Guide"); Assert.assertEquals(b.getAuthor(), "Cassandra Guru"); @@ -625,7 +625,7 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa List bookz = template.select(select.getQueryString(), Book.class); - log.info("Book Count -> " + bookz.size()); + log.debug("Book Count -> " + bookz.size()); Assert.assertEquals(bookz.size(), 20);