diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlOperations.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlOperations.java index 594ffc77d..fa9b5423b 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlOperations.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlOperations.java @@ -21,6 +21,14 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import org.springframework.cassandra.core.keyspace.AlterKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.AlterTableSpecification; +import org.springframework.cassandra.core.keyspace.CreateIndexSpecification; +import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.CreateTableSpecification; +import org.springframework.cassandra.core.keyspace.DropIndexSpecification; +import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.DropTableSpecification; import org.springframework.dao.DataAccessException; import com.datastax.driver.core.ResultSet; @@ -754,4 +762,59 @@ public interface CqlOperations { */ void truncate(String tableName); + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(DropTableSpecification specification); + + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(CreateTableSpecification specification); + + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(AlterTableSpecification specification); + + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(DropKeyspaceSpecification specification); + + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(CreateKeyspaceSpecification specification); + + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(AlterKeyspaceSpecification specification); + + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(DropIndexSpecification specification); + + /** + * Convenience method to convert the given specification to CQL and execute it. + * + * @param specification The specification to execute; must not be null. + */ + ResultSet execute(CreateIndexSpecification specification); } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlTemplate.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlTemplate.java index 6b90dee2a..52e8b034e 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlTemplate.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/CqlTemplate.java @@ -27,6 +27,22 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.springframework.cassandra.core.cql.generator.AlterKeyspaceCqlGenerator; +import org.springframework.cassandra.core.cql.generator.AlterTableCqlGenerator; +import org.springframework.cassandra.core.cql.generator.CreateIndexCqlGenerator; +import org.springframework.cassandra.core.cql.generator.CreateKeyspaceCqlGenerator; +import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; +import org.springframework.cassandra.core.cql.generator.DropIndexCqlGenerator; +import org.springframework.cassandra.core.cql.generator.DropKeyspaceCqlGenerator; +import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator; +import org.springframework.cassandra.core.keyspace.AlterKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.AlterTableSpecification; +import org.springframework.cassandra.core.keyspace.CreateIndexSpecification; +import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.CreateTableSpecification; +import org.springframework.cassandra.core.keyspace.DropIndexSpecification; +import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.DropTableSpecification; import org.springframework.cassandra.support.CassandraAccessor; import org.springframework.dao.DataAccessException; import org.springframework.dao.QueryTimeoutException; @@ -278,6 +294,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { process(doExecute(cql, options), rch); } + @Override public void query(String cql, RowCallbackHandler rch) throws DataAccessException { query(cql, rch, null); } @@ -287,26 +304,32 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { return process(doExecute(cql, options), rowMapper); } + @Override public List query(String cql, RowMapper rowMapper) throws DataAccessException { return query(cql, rowMapper, null); } + @Override public List> queryForListOfMap(String cql) throws DataAccessException { return processListOfMap(doExecute(cql, null)); } + @Override public List queryForList(String cql, Class elementType) throws DataAccessException { return processList(doExecute(cql, null), elementType); } + @Override public Map queryForMap(String cql) throws DataAccessException { return processMap(doExecute(cql, null)); } + @Override public T queryForObject(String cql, Class requiredType) throws DataAccessException { return processOne(doExecute(cql, null), requiredType); } + @Override public T queryForObject(String cql, RowMapper rowMapper) throws DataAccessException { return processOne(doExecute(cql, null), rowMapper); } @@ -766,6 +789,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { logger.debug("Executing prepared CQL query"); return execute(psc, new PreparedStatementCallback() { + @Override public T doInPreparedStatement(PreparedStatement ps) throws DriverException { ResultSet rs = null; BoundStatement bs = null; @@ -794,6 +818,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { logger.debug("Executing prepared CQL query"); execute(psc, new PreparedStatementCallback() { + @Override public Object doInPreparedStatement(PreparedStatement ps) throws DriverException { ResultSet rs = null; BoundStatement bs = null; @@ -822,6 +847,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { logger.debug("Executing prepared CQL query"); return execute(psc, new PreparedStatementCallback>() { + @Override public List doInPreparedStatement(PreparedStatement ps) throws DriverException { ResultSet rs = null; BoundStatement bs = null; @@ -843,4 +869,100 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { return query(psc, psb, rowMapper, null); } + @Override + public ResultSet execute(final DropTableSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(DropTableCqlGenerator.toCql(specification)); + } + }); + } + + @Override + public ResultSet execute(final CreateTableSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(CreateTableCqlGenerator.toCql(specification)); + } + }); + } + + @Override + public ResultSet execute(final AlterTableSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(AlterTableCqlGenerator.toCql(specification)); + } + }); + } + + @Override + public ResultSet execute(final DropKeyspaceSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(DropKeyspaceCqlGenerator.toCql(specification)); + } + }); + } + + @Override + public ResultSet execute(final CreateKeyspaceSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(CreateKeyspaceCqlGenerator.toCql(specification)); + } + }); + } + + @Override + public ResultSet execute(final AlterKeyspaceSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(AlterKeyspaceCqlGenerator.toCql(specification)); + } + }); + } + + @Override + public ResultSet execute(final DropIndexSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(DropIndexCqlGenerator.toCql(specification)); + } + }); + } + + @Override + public ResultSet execute(final CreateIndexSpecification specification) { + + return execute(new SessionCallback() { + + @Override + public ResultSet doInSession(Session s) throws DataAccessException { + return s.execute(CreateIndexCqlGenerator.toCql(specification)); + } + }); + } + } \ No newline at end of file diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java index b711c5a41..61180cc7f 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java @@ -17,6 +17,8 @@ package org.springframework.cassandra.core.cql; import java.util.regex.Pattern; +import com.datastax.driver.core.DataType; + public class CqlStringUtils { protected static final String SINGLE_QUOTE = "\'"; @@ -24,6 +26,8 @@ public class CqlStringUtils { protected static final String DOUBLE_QUOTE = "\""; protected static final String DOUBLE_DOUBLE_QUOTE = "\"\""; protected static final String EMPTY_STRING = ""; + protected static final String TYPE_PARAMETER_PREFIX = "<"; + protected static final String TYPE_PARAMETER_SUFFIX = ">"; public static StringBuilder noNull(StringBuilder sb) { return sb == null ? new StringBuilder() : sb; @@ -137,4 +141,34 @@ public class CqlStringUtils { public static String removeSingleQuotes(Object thing) { return thing == null ? (String) null : ((String) thing).replaceAll(SINGLE_QUOTE, EMPTY_STRING); } + + /** + * Renders the given {@link DataType} as a CQL string. + * + * @param dataType The {@link DataType} to render; must not be null. + */ + public static String toCql(DataType dataType) { + + if (dataType.getTypeArguments().isEmpty()) { + return dataType.getName().name(); + } + + StringBuilder s = new StringBuilder(); + s.append(dataType.getName().name()).append(TYPE_PARAMETER_PREFIX); + + boolean first = true; + + for (DataType argDataType : dataType.getTypeArguments()) { + + if (first) { + first = false; + } else { + s.append(','); + } + + s.append(argDataType.getName().name()); + } + + return s.append(TYPE_PARAMETER_SUFFIX).toString(); + } } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterKeyspaceCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterKeyspaceCqlGenerator.java index 596c99174..35d54be10 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterKeyspaceCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterKeyspaceCqlGenerator.java @@ -29,10 +29,15 @@ import org.springframework.cassandra.core.keyspace.Option; */ public class AlterKeyspaceCqlGenerator extends KeyspaceOptionsCqlGenerator { + public static String toCql(AlterKeyspaceSpecification specification) { + return new AlterKeyspaceCqlGenerator(specification).toCql(); + } + public AlterKeyspaceCqlGenerator(AlterKeyspaceSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { cql = noNull(cql); diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java index ad8404d2f..0fb0050a9 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/AlterTableCqlGenerator.java @@ -34,10 +34,15 @@ import org.springframework.cassandra.core.keyspace.TableOption; */ public class AlterTableCqlGenerator extends TableOptionsCqlGenerator { + public static String toCql(AlterTableSpecification specification) { + return new AlterTableCqlGenerator(specification).toCql(); + } + public AlterTableCqlGenerator(AlterTableSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { cql = noNull(cql); diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateIndexCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateIndexCqlGenerator.java index 61636fe3d..25c3151f4 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateIndexCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateIndexCqlGenerator.java @@ -28,10 +28,15 @@ import org.springframework.util.StringUtils; */ public class CreateIndexCqlGenerator extends IndexNameCqlGenerator { + public static String toCql(CreateIndexSpecification specification) { + return new CreateIndexCqlGenerator(specification).toCql(); + } + public CreateIndexCqlGenerator(CreateIndexSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { cql = noNull(cql); diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateKeyspaceCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateKeyspaceCqlGenerator.java index d0aa5c22b..58ef610b5 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateKeyspaceCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateKeyspaceCqlGenerator.java @@ -30,10 +30,15 @@ import org.springframework.cassandra.core.keyspace.Option; */ public class CreateKeyspaceCqlGenerator extends KeyspaceCqlGenerator { + public static String toCql(CreateKeyspaceSpecification specification) { + return new CreateKeyspaceCqlGenerator(specification).toCql(); + } + public CreateKeyspaceCqlGenerator(CreateKeyspaceSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { cql = noNull(cql); diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java index c13d456ab..cb490bf54 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/CreateTableCqlGenerator.java @@ -35,10 +35,15 @@ import org.springframework.cassandra.core.keyspace.Option; */ public class CreateTableCqlGenerator extends TableCqlGenerator { + public static String toCql(CreateTableSpecification specification) { + return new CreateTableCqlGenerator(specification).toCql(); + } + public CreateTableCqlGenerator(CreateTableSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { cql = noNull(cql); diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropIndexCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropIndexCqlGenerator.java index 99c8d2f79..f6d70cb37 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropIndexCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropIndexCqlGenerator.java @@ -27,10 +27,15 @@ import org.springframework.cassandra.core.keyspace.DropIndexSpecification; */ public class DropIndexCqlGenerator extends IndexNameCqlGenerator { + public static String toCql(DropIndexSpecification specification) { + return new DropIndexCqlGenerator(specification).toCql(); + } + public DropIndexCqlGenerator(DropIndexSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { return noNull(cql).append("DROP INDEX ") // .append(spec().getIfExists() ? "IF EXISTS " : "") diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropKeyspaceCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropKeyspaceCqlGenerator.java index 63382bd88..9abde57bc 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropKeyspaceCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropKeyspaceCqlGenerator.java @@ -26,10 +26,15 @@ import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; */ public class DropKeyspaceCqlGenerator extends KeyspaceNameCqlGenerator { + public static String toCql(DropKeyspaceSpecification specification) { + return new DropKeyspaceCqlGenerator(specification).toCql(); + } + public DropKeyspaceCqlGenerator(DropKeyspaceSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { return noNull(cql).append("DROP KEYSPACE ").append(spec().getIfExists() ? "IF EXISTS " : "") .append(spec().getNameAsIdentifier()).append(";"); diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java index 57a459b36..708482254 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/DropTableCqlGenerator.java @@ -26,10 +26,15 @@ import org.springframework.cassandra.core.keyspace.DropTableSpecification; */ public class DropTableCqlGenerator extends TableNameCqlGenerator { + public static String toCql(DropTableSpecification specification) { + return new DropTableCqlGenerator(specification).toCql(); + } + public DropTableCqlGenerator(DropTableSpecification specification) { super(specification); } + @Override public StringBuilder toCql(StringBuilder cql) { return noNull(cql).append("DROP TABLE ") // .append(spec().getIfExists() ? "IF EXISTS " : "") diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java index 10b1b6674..0f87f1e8c 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateTableSpecification.java @@ -47,6 +47,11 @@ public class CreateTableSpecification extends TableSpecificationdropTable().name(tableName). + * + * @param tableName The name of the table to drop. + */ + public static DropTableSpecification dropTable(String tableName) { + return new DropTableSpecification().name(tableName); + } } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java b/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java index 359605c01..b2c363a18 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java @@ -24,7 +24,6 @@ import com.datastax.driver.core.Session; /** * @author David Webb - * */ public class CassandraAccessor implements InitializingBean { @@ -56,9 +55,7 @@ public class CassandraAccessor implements InitializingBean { */ @Override public void afterPropertiesSet() { - if (getSession() == null) { - throw new IllegalArgumentException("Property 'session' is required"); - } + Assert.notNull(session); } /** diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java index cd3794260..8b30a0a98 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java @@ -7,6 +7,8 @@ import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.core.CassandraAdminTemplate; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.EntityMapping; +import org.springframework.data.cassandra.mapping.Mapping; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java index 0de077c04..5c06b1447 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.data.cassandra.config.java; +import java.lang.annotation.Annotation; import java.util.HashSet; import java.util.Set; @@ -27,7 +28,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.data.annotation.Persistent; import org.springframework.data.cassandra.config.CassandraDataSessionFactoryBean; -import org.springframework.data.cassandra.config.Mapping; import org.springframework.data.cassandra.config.SchemaAction; import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.convert.MappingCassandraConverter; @@ -35,6 +35,8 @@ import org.springframework.data.cassandra.core.CassandraAdminOperations; import org.springframework.data.cassandra.core.CassandraAdminTemplate; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.DefaultCassandraMappingContext; +import org.springframework.data.cassandra.mapping.Mapping; +import org.springframework.data.cassandra.mapping.PrimaryKeyClass; import org.springframework.data.cassandra.mapping.Table; import org.springframework.data.mapping.context.MappingContext; import org.springframework.util.ClassUtils; @@ -139,6 +141,7 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC false); componentProvider.addIncludeFilter(new AnnotationTypeFilter(Table.class)); componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(PrimaryKeyClass.class)); for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java index 9e0fe53f0..ea2327801 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java @@ -10,9 +10,9 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.cassandra.config.xml.CassandraSessionParser; import org.springframework.data.cassandra.config.DefaultDataBeanNames; import org.springframework.data.cassandra.config.CassandraDataSessionFactoryBean; -import org.springframework.data.cassandra.config.EntityMapping; -import org.springframework.data.cassandra.config.Mapping; import org.springframework.data.cassandra.config.SchemaAction; +import org.springframework.data.cassandra.mapping.EntityMapping; +import org.springframework.data.cassandra.mapping.Mapping; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Attr; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraPersistentEntityParameterValueProvider.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraPersistentEntityParameterValueProvider.java new file mode 100644 index 000000000..e33499cde --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraPersistentEntityParameterValueProvider.java @@ -0,0 +1,15 @@ +package org.springframework.data.cassandra.convert; + +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; +import org.springframework.data.mapping.model.PropertyValueProvider; + +public class CassandraPersistentEntityParameterValueProvider extends + PersistentEntityParameterValueProvider { + + public CassandraPersistentEntityParameterValueProvider(PersistentEntity entity, + PropertyValueProvider provider, Object parent) { + super(entity, provider, parent); + } +} 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 879cd8c1a..8315f0b19 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 @@ -31,8 +31,6 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; import org.springframework.data.mapping.model.MappingException; -import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; -import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.mapping.model.SpELContext; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -54,14 +52,14 @@ import com.datastax.driver.core.querybuilder.Update; public class MappingCassandraConverter extends AbstractCassandraConverter implements CassandraConverter, ApplicationContextAware, BeanClassLoaderAware { - protected static final Logger log = LoggerFactory.getLogger(MappingCassandraConverter.class); + protected final Logger log = LoggerFactory.getLogger(getClass()); protected final CassandraMappingContext mappingContext; protected ApplicationContext applicationContext; - private SpELContext spELContext; - private boolean useFieldAccessOnly = true; + protected SpELContext spELContext; + protected boolean useFieldAccessOnly = true; - private ClassLoader beanClassLoader; + protected ClassLoader beanClassLoader; /** * Creates a new {@link MappingCassandraConverter} with the given {@link CassandraMappingContext}. @@ -111,45 +109,48 @@ public class MappingCassandraConverter extends AbstractCassandraConverter implem final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(row, spELContext); - final PropertyValueProvider propertyProvider = new CassandraPropertyValueProvider(row, - evaluator); + final CassandraPropertyValueProvider propertyProvider = new CassandraPropertyValueProvider(row, evaluator); - PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider( + CassandraPersistentEntityParameterValueProvider parameterProvider = new CassandraPersistentEntityParameterValueProvider( entity, propertyProvider, null); EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); S instance = instantiator.createInstance(entity, parameterProvider); final BeanWrapper, S> wrapper = BeanWrapper.create(instance, conversionService); - S result = wrapper.getBean(); + + readPropertiesFromRow(entity, row, propertyProvider, wrapper); + + return wrapper.getBean(); + } + + protected void readPropertiesFromRow(final CassandraPersistentEntity entity, final Row row, + final CassandraPropertyValueProvider propertyProvider, final BeanWrapper wrapper) { entity.doWithProperties(new PropertyHandler() { @Override public void doWithPersistentProperty(CassandraPersistentProperty prop) { - MappingCassandraConverter.this.handlePersistentPropertyRead(row, entity, prop, propertyProvider, wrapper); + MappingCassandraConverter.this.readPropertyFromRow(row, entity, prop, propertyProvider, wrapper); } }); - - return result; } - protected void handlePersistentPropertyRead(final Row row, final CassandraPersistentEntity entity, - final CassandraPersistentProperty prop, - final PropertyValueProvider propertyProvider, final BeanWrapper wrapper) { + protected void readPropertyFromRow(final Row row, final CassandraPersistentEntity entity, + final CassandraPersistentProperty prop, final CassandraPropertyValueProvider propertyProvider, + final BeanWrapper wrapper) { if (entity.isConstructorArgument(prop)) { // skip 'cause prop was set in ctor return; } if (prop.isCompositePrimaryKey()) { - // handle composite primary key properties via recursion into this method - throw new UnsupportedOperationException("composite primary keys are TODO"); + readPropertiesFromRow(prop.getCompositePrimaryKeyEntity(), row, propertyProvider, wrapper); + return; } - boolean hasValueForProperty = row.getColumnDefinitions().contains(prop.getColumnName()); - if (!hasValueForProperty) { + if (!row.getColumnDefinitions().contains(prop.getColumnName())) { return; } @@ -174,103 +175,121 @@ public class MappingCassandraConverter extends AbstractCassandraConverter implem } @Override - public void write(Object obj, Object builtStatement) { + public void write(Object source, Object sink) { - if (obj == null) { + if (source == null) { return; } - Class beanClassLoaderClass = transformClassToBeanClassLoaderClass(obj.getClass()); + Class beanClassLoaderClass = transformClassToBeanClassLoaderClass(source.getClass()); CassandraPersistentEntity entity = mappingContext.getPersistentEntity(beanClassLoaderClass); if (entity == null) { - throw new MappingException("No mapping metadata found for " + obj.getClass()); + throw new MappingException("No mapping metadata found for " + source.getClass()); } - if (builtStatement instanceof Insert) { - writeInsertInternal(obj, (Insert) builtStatement, entity); - } else if (builtStatement instanceof Update) { - writeUpdateInternal(obj, (Update) builtStatement, entity); - } else if (builtStatement instanceof Where) { - writeDeleteWhereInternal(obj, (Where) builtStatement, entity); + if (sink instanceof Insert) { + writeInsertFromObject(source, (Insert) sink, entity); + } else if (sink instanceof Update) { + writeUpdateFromObject(source, (Update) sink, entity); + } else if (sink instanceof Where) { + writeDeleteWhereFromObject(source, (Where) sink, entity); } else { - throw new MappingException("Unknown buildStatement " + builtStatement.getClass().getName()); + throw new MappingException("Unknown buildStatement " + sink.getClass().getName()); } } - private void writeInsertInternal(final Object objectToSave, final Insert insert, CassandraPersistentEntity entity) { - - final BeanWrapper, Object> wrapper = BeanWrapper.create(objectToSave, - conversionService); - - // Write the properties - entity.doWithProperties(new PropertyHandler() { - @Override - public void doWithPersistentProperty(CassandraPersistentProperty prop) { - - Object propertyObj = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); - - if (propertyObj != null) { - insert.value(prop.getColumnName(), propertyObj); - } - - } - }); - + protected void writeInsertFromObject(final Object object, final Insert insert, CassandraPersistentEntity entity) { + writeInsertFromWrapper(BeanWrapper., Object> create(object, conversionService), + insert, entity); } - private void writeUpdateInternal(final Object objectToSave, final Update update, CassandraPersistentEntity entity) { + protected void writeInsertFromWrapper(final BeanWrapper, Object> wrapper, + final Insert insert, CassandraPersistentEntity entity) { - final BeanWrapper, Object> wrapper = BeanWrapper.create(objectToSave, - conversionService); - - // Write the properties entity.doWithProperties(new PropertyHandler() { + @Override public void doWithPersistentProperty(CassandraPersistentProperty prop) { - Object propertyObj = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); + Object value = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); - if (propertyObj != null) { - if (prop.isIdProperty()) { - update.where(QueryBuilder.eq(prop.getColumnName(), propertyObj)); + if (prop.isCompositePrimaryKey()) { + writeInsertFromWrapper( + BeanWrapper., Object> create(value, conversionService), insert, + prop.getCompositePrimaryKeyEntity()); + return; + } + + if (value != null) { + insert.value(prop.getColumnName(), value); + } + } + }); + } + + protected void writeUpdateFromObject(final Object object, final Update update, CassandraPersistentEntity entity) { + writeUpdateFromWrapper(BeanWrapper., Object> create(object, conversionService), + update, entity); + } + + protected void writeUpdateFromWrapper(final BeanWrapper, Object> wrapper, + final Update update, final CassandraPersistentEntity entity) { + + entity.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + Object value = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); + + if (prop.isCompositePrimaryKey()) { + writeUpdateFromWrapper( + BeanWrapper., Object> create(value, conversionService), update, + prop.getCompositePrimaryKeyEntity()); + return; + } + + if (value != null) { + if (prop.isIdProperty() || entity.isCompositePrimaryKey()) { + update.where(QueryBuilder.eq(prop.getColumnName(), value)); } else { - update.with(QueryBuilder.set(prop.getColumnName(), propertyObj)); + update.with(QueryBuilder.set(prop.getColumnName(), value)); } } - } }); - } - private void writeDeleteWhereInternal(final Object objectToSave, final Where whereId, - CassandraPersistentEntity entity) { + protected void writeDeleteWhereFromObject(final Object object, final Where where, CassandraPersistentEntity entity) { + writeDeleteWhereFromWrapper( + BeanWrapper., Object> create(object, conversionService), where, entity); + } - final BeanWrapper, Object> wrapper = BeanWrapper.create(objectToSave, - conversionService); + protected void writeDeleteWhereFromWrapper(final BeanWrapper, Object> wrapper, + final Where where, CassandraPersistentEntity entity) { - // Write the properties - entity.doWithProperties(new PropertyHandler() { - @Override - public void doWithPersistentProperty(CassandraPersistentProperty prop) { + CassandraPersistentProperty idProperty = entity.getIdProperty(); + Object idValue = wrapper.getProperty(idProperty, idProperty.getType(), useFieldAccessOnly); - if (prop.isIdProperty()) { + if (idValue == null) { + String msg = String.format("no id value found in object {}", wrapper.getBean()); + log.error(msg); + throw new IllegalArgumentException(msg); + } - Object propertyObj = wrapper.getProperty(prop, prop.getType(), useFieldAccessOnly); - - if (propertyObj != null) { - whereId.and(QueryBuilder.eq(prop.getColumnName(), propertyObj)); - } - } - - } - }); + if (idProperty.isCompositePrimaryKey()) { + writeDeleteWhereFromWrapper( + BeanWrapper., Object> create(idValue, conversionService), where, + idProperty.getCompositePrimaryKeyEntity()); + return; + } + where.and(QueryBuilder.eq(idProperty.getColumnName(), idValue)); } @SuppressWarnings("unchecked") - private Class transformClassToBeanClassLoaderClass(Class entity) { + protected Class transformClassToBeanClassLoaderClass(Class entity) { try { return (Class) ClassUtils.forName(entity.getName(), beanClassLoader); } catch (ClassNotFoundException e) { diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java index f15171ff7..42ace67b9 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java @@ -6,7 +6,12 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cassandra.core.SessionCallback; +import org.springframework.cassandra.core.cql.generator.AlterTableCqlGenerator; import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; +import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator; +import org.springframework.cassandra.core.keyspace.AlterTableSpecification; +import org.springframework.cassandra.core.keyspace.CreateTableSpecification; +import org.springframework.cassandra.core.keyspace.DropTableSpecification; import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.cassandra.convert.CassandraConverter; @@ -60,7 +65,9 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand @Override public void replaceTable(String tableName, Class entityClass, Map optionsByName) { - throw new UnsupportedOperationException("not yet implemented"); + + dropTable(tableName); + createTable(false, tableName, entityClass, optionsByName); } /** @@ -102,15 +109,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand log.info("Dropping table => " + tableName); - final String q = CqlUtils.dropTable(tableName); - log.info(q); - - execute(new SessionCallback() { - @Override - public ResultSet doInSession(Session s) { - return s.execute(q); - } - }); + execute(DropTableSpecification.dropTable(tableName)); } @Override diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java index db8a6c052..cdb608d30 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java @@ -30,7 +30,6 @@ import com.datastax.driver.core.querybuilder.Select; * @author Alex Shvid * @author David Webb * @author Matthew Adams - * */ public interface CassandraOperations extends CqlOperations { @@ -58,9 +57,10 @@ public interface CassandraOperations extends CqlOperations { * @param selectClass must not be {@literal null}, mapped entity type. * @return */ - List select(Select selectQuery, Class selectClass); + T selectOneById(Class selectClass, Object id); + /** * Execute query and convert ResultSet to the entity * @@ -72,13 +72,14 @@ public interface CassandraOperations extends CqlOperations { T selectOne(Select selectQuery, Class selectClass); + Long countById(Class clazz, Object id); + /** * Counts rows for given query * * @param selectQuery * @return */ - Long count(Select selectQuery); /** @@ -87,7 +88,6 @@ public interface CassandraOperations extends CqlOperations { * @param tableName * @return */ - Long count(String tableName); /** @@ -457,4 +457,8 @@ public interface CassandraOperations extends CqlOperations { * @return */ CassandraConverter getConverter(); + + void deleteById(Class clazz, Object id); + + List selectByIds(Class clazz, Iterable ids); } 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 9898088f1..577e6fafd 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 @@ -16,12 +16,9 @@ package org.springframework.data.cassandra.core; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Set; import org.springframework.cassandra.core.CqlTemplate; import org.springframework.cassandra.core.QueryOptions; @@ -30,53 +27,48 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.cassandra.convert.CassandraConverter; -import org.springframework.data.cassandra.convert.MappingCassandraConverter; -import org.springframework.data.cassandra.exception.EntityWriterException; 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.util.CqlUtils; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.convert.EntityWriter; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import com.datastax.driver.core.Query; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; +import com.datastax.driver.core.Statement; import com.datastax.driver.core.querybuilder.Batch; +import com.datastax.driver.core.querybuilder.Clause; +import com.datastax.driver.core.querybuilder.Delete; +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.Select; +import com.datastax.driver.core.querybuilder.Update; /** - * The Cassandra Data Template is a convenience API for all Cassandra Operations using POJOs. For low level Cassandra - * Operations use the {@link CqlTemplate} + * The CassandraTemplate is a convenient API for all Cassandra operations using POJOs with their Spring Data Cassandra + * mapping information. For low-level Cassandra operation, see {@link CqlTemplate}. * * @author Alex Shvid * @author David Webb * @author Matthew T. Adams + * + * @see CqlTemplate */ public class CassandraTemplate extends CqlTemplate implements CassandraOperations { - /* - * List of iterable classes when testing POJOs for specific operations. - */ - public static final Collection ITERABLE_CLASSES; - static { - - Set iterableClasses = new HashSet(); - iterableClasses.add(List.class.getName()); - iterableClasses.add(Collection.class.getName()); - iterableClasses.add(Iterator.class.getName()); - - ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses); - - } - /* * Required elements for successful Template Operations. These can be set with the Constructor, or wired in * later. */ private CassandraConverter cassandraConverter; private CassandraMappingContext mappingContext; + private boolean useFieldAccessOnly = false; /** * Default Constructor for wiring in the required components later @@ -84,15 +76,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation public CassandraTemplate() { } - /** - * Constructor if only session is known at time of Template Creation - * - * @param session must not be {@literal null} - */ - public CassandraTemplate(Session session) { - this(session, new MappingCassandraConverter(new DefaultCassandraMappingContext())); - } - /** * Constructor if only session and converter are known at time of Template Creation * @@ -101,23 +84,60 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation */ public CassandraTemplate(Session session, CassandraConverter converter) { setSession(session); - this.cassandraConverter = converter; - this.mappingContext = cassandraConverter.getCassandraMappingContext(); + setConverter(converter); + } + + public void setConverter(CassandraConverter cassandraConverter) { + + Assert.notNull(cassandraConverter); + this.cassandraConverter = cassandraConverter; + mappingContext = cassandraConverter.getCassandraMappingContext(); + Assert.notNull(mappingContext); + } + + @Override + public CassandraConverter getConverter() { + return cassandraConverter; } public CassandraMappingContext getCassandraMappingContext() { return mappingContext; } + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + + Assert.notNull(cassandraConverter); + Assert.notNull(mappingContext); + } + + @Override + public Long countById(Class clazz, Object id) { + + Assert.notNull(clazz); + Assert.notNull(id); + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(clazz); + if (entity == null) { + throw new IllegalArgumentException(String.format("unknown persistent class [%s]", clazz.getName())); + } + + Select select = QueryBuilder.select().countAll().from(entity.getTableName()); + appendIdCriteria(select.where(), entity, id); + + return count(select); + } + @Override public Long count(Select selectQuery) { - return doSelectCount(selectQuery); + return selectCount(selectQuery); } @Override public Long count(String tableName) { Select select = QueryBuilder.select().countAll().from(tableName); - return doSelectCount(select); + return selectCount(select); } @Override @@ -136,7 +156,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation @Override public void delete(List entities, String tableName) { - delete(entities, tableName, null); } @@ -145,7 +164,21 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entities); Assert.notEmpty(entities); Assert.notNull(tableName); - doBatchDelete(tableName, entities, options, false); + batchDelete(tableName, entities, options, false); + } + + @Override + public void deleteById(Class clazz, Object id) { + + Assert.notNull(clazz); + Assert.notNull(id); + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(clazz); + + Delete delete = QueryBuilder.delete().all().from(entity.getTableName()); + appendIdCriteria(delete.where(), entity, id); + + execute(delete.getQueryString()); } @Override @@ -171,7 +204,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation public void delete(T entity, String tableName, QueryOptions options) { Assert.notNull(entity); Assert.notNull(tableName); - doDelete(tableName, entity, options, false); + delete(tableName, entity, options, false); } @Override @@ -198,7 +231,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entities); Assert.notEmpty(entities); Assert.notNull(tableName); - doBatchDelete(tableName, entities, options, true); + batchDelete(tableName, entities, options, true); } @Override @@ -224,7 +257,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation public void deleteAsynchronously(T entity, String tableName, QueryOptions options) { Assert.notNull(entity); Assert.notNull(tableName); - doDelete(tableName, entity, options, true); + delete(tableName, entity, options, true); } /** @@ -246,11 +279,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation return entity.getTableName(); } - @Override - public CassandraConverter getConverter() { - return cassandraConverter; - } - @Override public String getTableName(Class entityClass) { return determineTableName(entityClass); @@ -280,7 +308,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entities); Assert.notEmpty(entities); Assert.notNull(tableName); - return doBatchInsert(tableName, entities, options, false); + return batchInsert(tableName, entities, options, false); } @Override @@ -307,7 +335,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entity); Assert.notNull(tableName); ensureNotIterable(entity); - return doInsert(tableName, entity, options, false); + return insert(tableName, entity, options, false); } @Override @@ -334,7 +362,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entities); Assert.notEmpty(entities); Assert.notNull(tableName); - return doBatchInsert(tableName, entities, options, true); + return batchInsert(tableName, entities, options, true); } @Override @@ -363,7 +391,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation ensureNotIterable(entity); - return doInsert(tableName, entity, options, true); + return insert(tableName, entity, options, true); } @Override @@ -373,7 +401,108 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation @Override public List select(String cql, Class selectClass) { - return doSelect(cql, new ReadRowCallback(cassandraConverter, selectClass)); + return select(cql, new ReadRowCallback(cassandraConverter, selectClass)); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public List selectByIds(Class clazz, Iterable ids) { + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(clazz); + if (entity == null) { + throw new IllegalArgumentException(String.format("unknown persistent entity class [%s]", clazz.getName())); + } + if (entity.getIdProperty().isCompositePrimaryKey()) { + throw new IllegalArgumentException(String.format( + "entity class [%s] uses a composite primary key class [%s] which this method can't support", clazz.getName(), + entity.getIdProperty().getCompositePrimaryKeyEntity().getType().getName())); + } + + List idList = null; + if (ids instanceof List) { + idList = (List) ids; + } else { + idList = new ArrayList(); + for (Object id : ids) { + idList.add(id); + } + } + + Select select = QueryBuilder.select().all().from(entity.getTableName()); + select.where(QueryBuilder.in(entity.getIdProperty().getColumnName(), idList.toArray())); + + return select(select, clazz); + } + + @Override + public T selectOneById(Class selectClass, Object id) { + + Assert.notNull(selectClass); + Assert.notNull(id); + + CassandraPersistentEntity entityClass = mappingContext.getPersistentEntity(selectClass); + if (entityClass == null) { + throw new IllegalArgumentException(String.format("unknown entity class [%s]", selectClass.getName())); + } + + Select select = QueryBuilder.select().all().from(entityClass.getTableName()); + appendIdCriteria(select.where(), entityClass, id); + + return selectOne(select, selectClass); + } + + protected interface ClauseCallback { + void onClause(Clause clause); + } + + protected void appendIdCriteria(final ClauseCallback clauseCallback, CassandraPersistentEntity entity, Object id) { + + CassandraPersistentProperty idProperty = entity.getIdProperty(); + + if (idProperty.isCompositePrimaryKey()) { + + CassandraPersistentEntity idEntity = idProperty.getCompositePrimaryKeyEntity(); + + final BeanWrapper, Object> idWrapper = BeanWrapper + ., Object> create(id, cassandraConverter.getConversionService()); + + idEntity.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(CassandraPersistentProperty p) { + + clauseCallback.onClause(QueryBuilder.eq(p.getColumnName(), + idWrapper.getProperty(p, p.getActualType(), useFieldAccessOnly))); + } + }); + + return; + } + + clauseCallback.onClause(QueryBuilder.eq(idProperty.getColumnName(), id)); + } + + protected void appendIdCriteria(final com.datastax.driver.core.querybuilder.Select.Where where, + CassandraPersistentEntity entity, Object id) { + + appendIdCriteria(new ClauseCallback() { + + @Override + public void onClause(Clause clause) { + where.and(clause); + } + }, entity, id); + } + + protected void appendIdCriteria(final Where where, CassandraPersistentEntity entity, Object id) { + + appendIdCriteria(new ClauseCallback() { + + @Override + public void onClause(Clause clause) { + where.and(clause); + } + }, entity, id); } @Override @@ -383,7 +512,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation @Override public T selectOne(String cql, Class selectClass) { - return doSelectOne(cql, new ReadRowCallback(cassandraConverter, selectClass)); + return selectOne(cql, new ReadRowCallback(cassandraConverter, selectClass)); } @Override @@ -410,7 +539,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entities); Assert.notEmpty(entities); Assert.notNull(tableName); - return doBatchUpdate(tableName, entities, options, false); + return batchUpdate(tableName, entities, options, false); } @Override @@ -436,7 +565,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation public T update(T entity, String tableName, QueryOptions options) { Assert.notNull(entity); Assert.notNull(tableName); - return doUpdate(tableName, entity, options, false); + return update(tableName, entity, options, false); } @Override @@ -463,7 +592,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(entities); Assert.notEmpty(entities); Assert.notNull(tableName); - return doBatchUpdate(tableName, entities, options, true); + return batchUpdate(tableName, entities, options, true); } @Override @@ -490,7 +619,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation public T updateAsynchronously(T entity, String tableName, QueryOptions options) { Assert.notNull(entity); Assert.notNull(tableName); - return doUpdate(tableName, entity, options, true); + return update(tableName, entity, options, true); } /** @@ -505,12 +634,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation return null; } - /** - * @param query - * @param readRowCallback - * @return - */ - private List doSelect(final String query, ReadRowCallback readRowCallback) { + private List select(final String query, ReadRowCallback readRowCallback) { ResultSet resultSet = doExecute(new SessionCallback() { @@ -534,11 +658,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation return result; } - /** - * @param selectQuery - * @return - */ - private Long doSelectCount(final Select query) { + private Long selectCount(final Select query) { Long count = null; @@ -569,7 +689,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param readRowCallback * @return */ - private T doSelectOne(final String query, ReadRowCallback readRowCallback) { + private T selectOne(final String query, ReadRowCallback readRowCallback) { logger.info(query); @@ -607,36 +727,29 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param tableName * @param objectToRemove */ - protected void doBatchDelete(final String tableName, final List entities, final QueryOptions options, + protected void batchDelete(final String tableName, final List entities, final QueryOptions options, final boolean deleteAsynchronously) { Assert.notEmpty(entities); - try { + final Batch b = createDeleteBatchQuery(tableName, entities, options, cassandraConverter); + logger.info(b.toString()); - final Batch b = CqlUtils.toDeleteBatchQuery(tableName, entities, options, cassandraConverter); - logger.info(b.toString()); + doExecute(new SessionCallback() { - doExecute(new SessionCallback() { - - @Override - public Object doInSession(Session s) throws DataAccessException { - - if (deleteAsynchronously) { - s.executeAsync(b); - } else { - s.execute(b); - } - - return null; + @Override + public Object doInSession(Session s) throws DataAccessException { + if (deleteAsynchronously) { + s.executeAsync(b); + } else { + s.execute(b); } - }); - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); - } + return null; + + } + }); } /** @@ -648,36 +761,29 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param insertAsychronously * @return */ - protected List doBatchInsert(final String tableName, final List entities, final QueryOptions options, + protected List batchInsert(final String tableName, final List entities, final QueryOptions options, final boolean insertAsychronously) { Assert.notEmpty(entities); - try { + final Batch b = createInsertBatchQuery(tableName, entities, options, cassandraConverter); + logger.info(b.getQueryString()); - final Batch b = CqlUtils.toInsertBatchQuery(tableName, entities, options, cassandraConverter); - logger.info(b.getQueryString()); + return doExecute(new SessionCallback>() { - return doExecute(new SessionCallback>() { - - @Override - public List doInSession(Session s) throws DataAccessException { - - if (insertAsychronously) { - s.executeAsync(b); - } else { - s.execute(b); - } - - return entities; + @Override + public List doInSession(Session s) throws DataAccessException { + if (insertAsychronously) { + s.executeAsync(b); + } else { + s.execute(b); } - }); - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); - } + return entities; + + } + }); } /** @@ -689,36 +795,29 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param updateAsychronously * @return */ - protected List doBatchUpdate(final String tableName, final List entities, final QueryOptions options, + protected List batchUpdate(final String tableName, final List entities, final QueryOptions options, final boolean updateAsychronously) { Assert.notEmpty(entities); - try { + final Batch b = toUpdateBatchQuery(tableName, entities, options, cassandraConverter); + logger.info(b.toString()); - final Batch b = CqlUtils.toUpdateBatchQuery(tableName, entities, options, cassandraConverter); - logger.info(b.toString()); + return doExecute(new SessionCallback>() { - return doExecute(new SessionCallback>() { - - @Override - public List doInSession(Session s) throws DataAccessException { - - if (updateAsychronously) { - s.executeAsync(b); - } else { - s.execute(b); - } - - return entities; + @Override + public List doInSession(Session s) throws DataAccessException { + if (updateAsychronously) { + s.executeAsync(b); + } else { + s.execute(b); } - }); - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); - } + return entities; + + } + }); } /** @@ -727,54 +826,26 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param tableName * @param objectToRemove */ - protected void doDelete(final String tableName, final T objectToRemove, final QueryOptions options, + protected void delete(final String tableName, final T objectToRemove, final QueryOptions options, final boolean deleteAsynchronously) { - try { + final Query q = createDeleteQuery(tableName, objectToRemove, options, cassandraConverter); + logger.info(q.toString()); - final Query q = CqlUtils.toDeleteQuery(tableName, objectToRemove, options, cassandraConverter); - logger.info(q.toString()); + doExecute(new SessionCallback() { - doExecute(new SessionCallback() { - - @Override - public Object doInSession(Session s) throws DataAccessException { - - if (deleteAsynchronously) { - s.executeAsync(q); - } else { - s.execute(q); - } - - return null; + @Override + public Object doInSession(Session s) throws DataAccessException { + if (deleteAsynchronously) { + s.executeAsync(q); + } else { + s.execute(q); } - }); - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); - } - } - - /** - * Execute a command at the Session Level - * - * @param callback - * @return - */ - @Override - protected T doExecute(SessionCallback callback) { - - Assert.notNull(callback); - - try { - - return callback.doInSession(getSession()); - - } catch (DataAccessException e) { - throw translateExceptionIfPossible(e); - } + return null; + } + }); } /** @@ -783,42 +854,34 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param tableName * @param entity */ - protected T doInsert(final String tableName, final T entity, final QueryOptions options, + protected T insert(final String tableName, final T entity, final QueryOptions options, final boolean insertAsychronously) { - try { + final Query q = createInsertQuery(tableName, entity, options, cassandraConverter); - final Query q = CqlUtils.toInsertQuery(tableName, entity, options, cassandraConverter); - - logger.info(q.toString()); - if (q.getConsistencyLevel() != null) { - logger.info(q.getConsistencyLevel().name()); - } - if (q.getRetryPolicy() != null) { - logger.info(q.getRetryPolicy().toString()); - } - - return doExecute(new SessionCallback() { - - @Override - public T doInSession(Session s) throws DataAccessException { - - if (insertAsychronously) { - s.executeAsync(q); - } else { - s.execute(q); - } - - return entity; - - } - }); - - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); + logger.info(q.toString()); + if (q.getConsistencyLevel() != null) { + logger.info(q.getConsistencyLevel().name()); + } + if (q.getRetryPolicy() != null) { + logger.info(q.getRetryPolicy().toString()); } + return doExecute(new SessionCallback() { + + @Override + public T doInSession(Session s) throws DataAccessException { + + if (insertAsychronously) { + s.executeAsync(q); + } else { + s.execute(q); + } + + return entity; + + } + }); } /** @@ -830,35 +893,27 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param updateAsychronously * @return */ - protected T doUpdate(final String tableName, final T entity, final QueryOptions options, + protected T update(final String tableName, final T entity, final QueryOptions options, final boolean updateAsychronously) { - try { + final Query q = toUpdateQuery(tableName, entity, options, cassandraConverter); + logger.info(q.toString()); - final Query q = CqlUtils.toUpdateQuery(tableName, entity, options, cassandraConverter); - logger.info(q.toString()); + return doExecute(new SessionCallback() { - return doExecute(new SessionCallback() { - - @Override - public T doInSession(Session s) throws DataAccessException { - - if (updateAsychronously) { - s.executeAsync(q); - } else { - s.execute(q); - } - - return entity; + @Override + public T doInSession(Session s) throws DataAccessException { + if (updateAsychronously) { + s.executeAsync(q); + } else { + s.execute(q); } - }); - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); - } + return entity; + } + }); } /** @@ -867,10 +922,197 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param o */ protected void ensureNotIterable(Object o) { - if (null != o) { - if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) { - throw new IllegalArgumentException("Cannot use a collection here."); - } + + if (null == o) { + return; + } + + if (o.getClass().isArray() || (o instanceof Iterable) || (o instanceof Iterator)) { + throw new IllegalArgumentException("cannot use a multivalued object here."); } } + + /** + * Generates a Query Object for an insert + * + * @param tableName + * @param objectToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + */ + public static Query createInsertQuery(String tableName, final Object objectToSave, QueryOptions options, + EntityWriter entityWriter) { + + final Insert q = QueryBuilder.insertInto(tableName); + + /* + * Write properties + */ + entityWriter.write(objectToSave, q); + + /* + * Add Query Options + */ + CqlTemplate.addQueryOptions(q, options); + + /* + * Add TTL to Insert object + */ + if (options != null && options.getTtl() != null) { + q.using(QueryBuilder.ttl(options.getTtl())); + } + + return q; + + } + + /** + * Generates a Query Object for an Update + * + * @param tableName + * @param objectToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + */ + public static Query toUpdateQuery(String tableName, final Object objectToSave, QueryOptions options, + EntityWriter entityWriter) { + + final Update q = QueryBuilder.update(tableName); + + /* + * Write properties + */ + entityWriter.write(objectToSave, q); + + /* + * Add Query Options + */ + CqlTemplate.addQueryOptions(q, options); + + /* + * Add TTL to Insert object + */ + if (options != null && options.getTtl() != null) { + q.using(QueryBuilder.ttl(options.getTtl())); + } + + return q; + + } + + /** + * Generates a Batch Object for multiple Updates + * + * @param tableName + * @param objectsToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + */ + public static Batch toUpdateBatchQuery(final String tableName, final List objectsToSave, QueryOptions options, + EntityWriter entityWriter) { + + /* + * Return variable is a Batch statement + */ + final Batch b = QueryBuilder.batch(); + + for (final T objectToSave : objectsToSave) { + + b.add((Statement) toUpdateQuery(tableName, objectToSave, options, entityWriter)); + + } + + /* + * Add Query Options + */ + CqlTemplate.addQueryOptions(b, options); + + return b; + + } + + /** + * Generates a Batch Object for multiple inserts + * + * @param tableName + * @param entities + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); + */ + public static Batch createInsertBatchQuery(final String tableName, final List entities, QueryOptions options, + EntityWriter entityWriter) { + + Batch batch = QueryBuilder.batch(); + + for (T entity : entities) { + batch.add((Statement) createInsertQuery(tableName, entity, options, entityWriter)); + } + + CqlTemplate.addQueryOptions(batch, options); + + return batch; + } + + /** + * Create a Delete Query Object from an annotated POJO + * + * @param tableName + * @param object + * @param entity + * @param optionsByName + * @return + */ + public static Query createDeleteQuery(String tableName, final Object object, QueryOptions options, + EntityWriter entityWriter) { + + Delete.Selection ds = QueryBuilder.delete(); + Delete q = ds.from(tableName); + Where w = q.where(); + + entityWriter.write(object, w); + + CqlTemplate.addQueryOptions(q, options); + + return q; + } + + /** + * Create a Batch Query object for multiple deletes. + * + * @param tableName + * @param entities + * @param entity + * @param optionsByName + * + * @return + */ + public static Batch createDeleteBatchQuery(String tableName, List entities, QueryOptions options, + EntityWriter entityWriter) { + + Batch batch = QueryBuilder.batch(); + + for (T entity : entities) { + batch.add((Statement) createDeleteQuery(tableName, entity, options, entityWriter)); + } + + CqlTemplate.addQueryOptions(batch, options); + + return batch; + } + + public boolean getUseFieldAccessOnly() { + return useFieldAccessOnly; + } + + public void setUseFieldAccessOnly(boolean useFieldAccessOnly) { + this.useFieldAccessOnly = useFieldAccessOnly; + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/exception/EntityWriterException.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/exception/EntityWriterException.java deleted file mode 100644 index fff15ad98..000000000 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/exception/EntityWriterException.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2011-2013 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.exception; - -/** - * Exception to handle failing to write a PersistedEntity to a CQL String or Query object - * - * @author David Webb - * - */ -public class EntityWriterException extends Exception { - - private static final long serialVersionUID = -3068204776019978031L; - - /** - * @param message - */ - public EntityWriterException(String message) { - super(message); - } - - /** - * @param cause - */ - public EntityWriterException(Throwable cause) { - super(cause); - } - - /** - * @param message - * @param cause - */ - public EntityWriterException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java index c0965a4cd..863e8f838 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java @@ -15,6 +15,9 @@ */ package org.springframework.data.cassandra.mapping; +import java.util.ArrayList; +import java.util.List; + import org.springframework.beans.BeansException; import org.springframework.cassandra.support.exception.UnsupportedCassandraOperationException; import org.springframework.context.ApplicationContext; @@ -24,6 +27,7 @@ import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.data.cassandra.util.CassandraNamingUtils; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; +import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.util.TypeInformation; import org.springframework.expression.Expression; @@ -34,8 +38,7 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Cassandra specific {@link BasicPersistentEntity} implementation that adds Cassandra specific metadata such as the - * table name. + * Cassandra specific {@link BasicPersistentEntity} implementation that adds Cassandra specific metadata. * * @author Alex Shvid * @author Matthew T. Adams @@ -43,10 +46,14 @@ import org.springframework.util.StringUtils; public class BasicCassandraPersistentEntity extends BasicPersistentEntity implements CassandraPersistentEntity, ApplicationContextAware { - private String tableName; - private final SpelExpressionParser spelParser; - private final StandardEvaluationContext spelContext; - private final Class type; + protected String tableName; + protected CassandraMappingContext mappingContext; + protected final SpelExpressionParser spelParser; + protected final StandardEvaluationContext spelContext; + + public BasicCassandraPersistentEntity(TypeInformation typeInformation) { + this(typeInformation, null); + } /** * Creates a new {@link BasicCassandraPersistentEntity} with the given {@link TypeInformation}. Will default the table @@ -54,23 +61,22 @@ public class BasicCassandraPersistentEntity extends BasicPersistentEntity typeInformation) { + public BasicCassandraPersistentEntity(TypeInformation typeInformation, CassandraMappingContext mappingContext) { - super(typeInformation, DefaultCassandraPersistentPropertyColumnComparator.IT); + super(typeInformation, CassandraPersistentPropertyComparator.IT); this.spelParser = new SpelExpressionParser(); this.spelContext = new StandardEvaluationContext(); - - this.type = typeInformation.getType(); + this.mappingContext = mappingContext; determineTableName(); } protected void determineTableName() { - Table anno = type.getAnnotation(Table.class); + Table anno = getType().getAnnotation(Table.class); this.tableName = anno != null && StringUtils.hasText(anno.value()) ? anno.value() : CassandraNamingUtils - .getPreferredTableName(type); + .getPreferredTableName(getType()); } @Override @@ -102,4 +108,46 @@ public class BasicCassandraPersistentEntity extends BasicPersistentEntity getCompositePrimaryKeyProperties() { + + final List properties = new ArrayList(); + + if (!isCompositePrimaryKey()) { + throw new IllegalStateException(String.format("[%s] does not represent a composite primary key class", this + .getType().getName())); + } + + addCompositePrimaryKeyProperties(this, properties); + + return properties; + } + + protected void addCompositePrimaryKeyProperties(CassandraPersistentEntity compositePrimaryKeyEntity, + final List properties) { + + compositePrimaryKeyEntity.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(CassandraPersistentProperty p) { + + if (p.isCompositePrimaryKey()) { + addCompositePrimaryKeyProperties(p.getCompositePrimaryKeyEntity(), properties); + } else { + properties.add(p); + } + } + }); + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java index 651ab572b..b1e37bc67 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java @@ -17,6 +17,7 @@ package org.springframework.data.cassandra.mapping; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -24,6 +25,7 @@ import org.springframework.cassandra.core.Ordering; import org.springframework.cassandra.core.PrimaryKeyType; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -49,15 +51,20 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP */ public BasicCassandraPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, CassandraPersistentEntity owner, CassandraSimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); } + @Override + public CassandraPersistentEntity getOwner() { + return (CassandraPersistentEntity) super.getOwner(); + } + @Override public boolean isCompositePrimaryKey() { return getField().getType().isAnnotationPresent(PrimaryKeyClass.class); } - @Override public Class getCompositePrimaryKeyType() { if (!isCompositePrimaryKey()) { return null; @@ -67,31 +74,23 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP } @Override - public CassandraPersistentEntity getCompositePrimaryKeyEntity() { + public TypeInformation getCompositePrimaryKeyTypeInformation() { if (!isCompositePrimaryKey()) { return null; } - return (CassandraPersistentEntity) ClassTypeInformation.from(getCompositePrimaryKeyType()); + return ClassTypeInformation.from(getCompositePrimaryKeyType()); } @Override public String getColumnName() { - // first check @Column annotation - Column column = findAnnotation(Column.class); - if (column != null && StringUtils.hasText(column.value())) { - return column.value(); + List columnNames = getColumnNames(); + if (columnNames.size() != 1) { + throw new IllegalStateException("property does not have a single column mapping"); } - // else check @PrimaryKeyColumn annotation - PrimaryKeyColumn pk = findAnnotation(PrimaryKeyColumn.class); - if (pk != null && StringUtils.hasText(pk.name())) { - return pk.name(); - } - - // else default - return field.getName().toLowerCase(); // TODO: replace with naming strategy class + return columnNames.get(0); } @Override @@ -199,11 +198,6 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP return isAnnotationPresent(PrimaryKeyColumn.class); } - @Override - protected Association createAssociation() { - return new Association(this, null); - } - protected DataType getDataTypeFor(DataType.Name typeName) { DataType dataType = CassandraSimpleTypeHolder.getDataTypeFor(typeName); if (dataType == null) { @@ -230,4 +224,79 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); } } + + @Override + public List getColumnNames() { + + final List columnNames = new ArrayList(); + + if (isCompositePrimaryKey()) { + addCompositePrimaryKeyColumnNames(getCompositePrimaryKeyEntity(), columnNames); + return columnNames; + } + + // else not a composite primary key property -- first check @Column annotation + Column column = findAnnotation(Column.class); + if (column != null && StringUtils.hasText(column.value())) { + columnNames.add(column.value()); + return columnNames; + } + + // else check @PrimaryKeyColumn annotation + PrimaryKeyColumn pk = findAnnotation(PrimaryKeyColumn.class); + if (pk != null && StringUtils.hasText(pk.name())) { + columnNames.add(pk.name()); + return columnNames; + } + + // else default + columnNames.add(field.getName().toLowerCase()); // TODO: replace with naming strategy class + return columnNames; + } + + protected void addCompositePrimaryKeyColumnNames(CassandraPersistentEntity compositePrimaryKeyEntity, + final List columnNames) { + + compositePrimaryKeyEntity.doWithProperties(new PropertyHandler() { + + @Override + public void doWithPersistentProperty(CassandraPersistentProperty p) { + if (p.isCompositePrimaryKey()) { + addCompositePrimaryKeyColumnNames(p.getCompositePrimaryKeyEntity(), columnNames); + } else { + columnNames.add(p.getColumnName()); + } + } + }); + } + + @Override + public List getCompositePrimaryKeyProperties() { + + if (!isCompositePrimaryKey()) { + throw new IllegalStateException(String.format("[%s] does not represent a composite primary key property", + getField())); + } + + return getCompositePrimaryKeyEntity().getCompositePrimaryKeyProperties(); + } + + @Override + public CassandraPersistentEntity getCompositePrimaryKeyEntity() { + CassandraMappingContext mappingContext = getOwner().getMappingContext(); + if (mappingContext == null) { + throw new IllegalStateException("need CassandraMappingContext"); + } + return mappingContext.getPersistentEntity(getCompositePrimaryKeyTypeInformation()); + } + + @Override + public Association getAssociation() { + throw new UnsupportedOperationException("Cassandra does not support associations"); + } + + @Override + protected Association createAssociation() { + return new Association(this, null); + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentEntity.java new file mode 100644 index 000000000..eec3d79a5 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentEntity.java @@ -0,0 +1,64 @@ +package org.springframework.data.cassandra.mapping; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.data.util.TypeInformation; + +public class CachingCassandraPersistentEntity extends BasicCassandraPersistentEntity { + + protected String tableName; + protected String name; + protected Boolean isCompositePrimaryKey; + protected List compositePrimaryKeyProperties; + protected Map properties = new HashMap(); + + public CachingCassandraPersistentEntity(TypeInformation typeInformation) { + super(typeInformation); + } + + public CachingCassandraPersistentEntity(TypeInformation typeInformation, CassandraMappingContext mappingContext) { + super(typeInformation, mappingContext); + } + + @Override + public String getTableName() { + if (tableName == null) { + tableName = super.getTableName(); + } + return tableName; + } + + @Override + public String getName() { + if (name == null) { + name = super.getName(); + } + return name; + } + + @Override + public boolean isCompositePrimaryKey() { + if (isCompositePrimaryKey == null) { + isCompositePrimaryKey = super.isCompositePrimaryKey(); + } + return isCompositePrimaryKey; + } + + @Override + public List getCompositePrimaryKeyProperties() { + if (compositePrimaryKeyProperties == null) { + compositePrimaryKeyProperties = super.getCompositePrimaryKeyProperties(); + } + return compositePrimaryKeyProperties; + } + + @Override + public CassandraPersistentProperty getPersistentProperty(String name) { + if (properties.get(name) == null) { + properties.put(name, super.getPersistentProperty(name)); + } + return properties.get(name); + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java index 06ddc4aaa..4ac138f84 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java @@ -17,8 +17,10 @@ package org.springframework.data.cassandra.mapping; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; +import java.util.List; import org.springframework.cassandra.core.Ordering; +import org.springframework.data.util.TypeInformation; import com.datastax.driver.core.DataType; @@ -37,11 +39,12 @@ public class CachingCassandraPersistentProperty extends BasicCassandraPersistent private Boolean isClusterKeyColumn; private Boolean isPrimaryKeyColumn; private String columnName; + private List columnNames; private Ordering ordering; private boolean orderingCached = false; private DataType dataType; private Class compositePrimaryKeyType; - private CassandraPersistentEntity compositePrimaryKeyEntity; + private TypeInformation compositePrimaryKeyTypeInformation; /** * Creates a new {@link CachingCassandraPersistentProperty}. @@ -52,12 +55,12 @@ public class CachingCassandraPersistentProperty extends BasicCassandraPersistent } @Override - public CassandraPersistentEntity getCompositePrimaryKeyEntity() { + public TypeInformation getCompositePrimaryKeyTypeInformation() { - if (compositePrimaryKeyEntity == null) { - compositePrimaryKeyEntity = super.getCompositePrimaryKeyEntity(); + if (compositePrimaryKeyTypeInformation == null) { + compositePrimaryKeyTypeInformation = super.getCompositePrimaryKeyTypeInformation(); } - return compositePrimaryKeyEntity; + return compositePrimaryKeyTypeInformation; } @Override @@ -150,4 +153,12 @@ public class CachingCassandraPersistentProperty extends BasicCassandraPersistent } return isPartitionKeyColumn; } + + @Override + public List getColumnNames() { + if (columnNames == null) { + columnNames = super.getColumnNames(); + } + return columnNames; + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraColumnAnnotationComparator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraColumnAnnotationComparator.java new file mode 100644 index 000000000..3cb8f1836 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraColumnAnnotationComparator.java @@ -0,0 +1,21 @@ +package org.springframework.data.cassandra.mapping; + +import java.util.Comparator; + +/** + * {@link Comparator} implementation that uses {@link Column#value()}. + * + * @author Matthew T. Adams + */ +public enum CassandraColumnAnnotationComparator implements Comparator { + + /** + * The sole instance of this class. + */ + IT; + + @Override + public int compare(Column left, Column right) { + return left.value().compareTo(right.value()); + } +} \ No newline at end of file diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java index 3c34d1085..d6d60e0d6 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java @@ -15,6 +15,9 @@ */ package org.springframework.data.cassandra.mapping; +import java.util.List; + +import org.springframework.context.ApplicationContextAware; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.MutablePersistentEntity; @@ -24,7 +27,15 @@ import org.springframework.data.mapping.model.MutablePersistentEntity; * @author Alex Shvid * @author Matthew T. Adams */ -public interface CassandraPersistentEntity extends MutablePersistentEntity { +public interface CassandraPersistentEntity extends MutablePersistentEntity, + ApplicationContextAware { + + /** + * Returns whether this entity represents a composite primary key. + */ + boolean isCompositePrimaryKey(); + + List getCompositePrimaryKeyProperties(); /** * Returns the table name to which the entity shall be persisted. @@ -37,4 +48,6 @@ public interface CassandraPersistentEntity extends MutablePersistentEntity getCompositePrimaryKeyType(); - /** * Returns a {@link CassandraPersistentEntity} representing the composite primary key class of this entity, or null if * this class does not use a composite primary key. @@ -46,22 +43,42 @@ public interface CassandraPersistentProperty extends PersistentProperty getCompositePrimaryKeyEntity(); /** - * The name of the column to which a property is persisted. + * Returns a {@link TypeInformation} representing the type of the composite primary key class of this entity, or null + * if this class does not use a composite primary key. + */ + TypeInformation getCompositePrimaryKeyTypeInformation(); + + /** + * Gets the list of composite primary key properties that this composite primary key field is a placeholder for. + */ + List getCompositePrimaryKeyProperties(); + + /** + * The name of the single column to which the property is persisted. This is a convenience method when the caller + * knows that the property is mapped to a single column. Throws {@link IllegalStateException} if this property is + * mapped to multiple columns. */ String getColumnName(); /** - * The ordering for the column. Valid only for clustered columns. + * The names of the columns to which the property is persisted if this is a composite primary key property. Never + * returns null. + */ + List getColumnNames(); + + /** + * The ordering (ascending or descending) for the column. Valid only for primary key columns; returns null for + * non-primary key columns. */ Ordering getPrimaryKeyOrdering(); /** - * The column's data type. + * The column's data type. Not valid for a composite primary key, in which case this method returns null. */ DataType getDataType(); /** - * Whether the property has secondary index on this column. + * Whether the property has a secondary index on this column. */ boolean isIndexed(); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentPropertyComparator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentPropertyComparator.java new file mode 100644 index 000000000..9b52d6b90 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentPropertyComparator.java @@ -0,0 +1,87 @@ +package org.springframework.data.cassandra.mapping; + +import java.lang.reflect.Field; +import java.util.Comparator; + +/** + * {@link Comparator} implementation that orders {@link CassandraPersistentProperty} instances. + *

+ * Composite primary key properties and primary key properties sort before non-primary key properties. + * + * @author Alex Shvid + * @author Matthew T. Adams + */ +public enum CassandraPersistentPropertyComparator implements Comparator { + + /** + * The sole instance of this class. + */ + IT; + + @Override + public int compare(CassandraPersistentProperty left, CassandraPersistentProperty right) { + + if (left != null && right == null) { + return -1; + } + if (left == null && right != null) { + return 1; + } + if (left == null && right == null) { + return 0; + } + + if (left.equals(right)) { + return 0; + } + + boolean leftIsCompositePrimaryKey = left.isCompositePrimaryKey(); + boolean rightIsCompositePrimaryKey = right.isCompositePrimaryKey(); + + if (leftIsCompositePrimaryKey && rightIsCompositePrimaryKey) { + return 0; + } + + boolean leftIsPrimaryKey = left.isPrimaryKeyColumn(); + boolean rightIsPrimaryKey = right.isPrimaryKeyColumn(); + + Field leftField = left.getField(); + Field rightField = right.getField(); + + if (leftIsPrimaryKey && rightIsPrimaryKey) { + return CassandraPrimaryKeyColumnAnnotationComparator.IT.compare(leftField.getAnnotation(PrimaryKeyColumn.class), + rightField.getAnnotation(PrimaryKeyColumn.class)); + } + + boolean leftIsKey = leftIsCompositePrimaryKey || leftIsPrimaryKey; + boolean rightIsKey = rightIsCompositePrimaryKey || rightIsPrimaryKey; + + if (leftIsKey && !rightIsKey) { + return 1; + } + + if (!leftIsKey && rightIsKey) { + return -1; + } + + // else, neither property is a composite primary key nor a primary key; compare @Column annotations + + Column leftColumn = leftField.getAnnotation(Column.class); + Column rightColumn = rightField.getAnnotation(Column.class); + + if (leftColumn == null && rightColumn == null) { + return leftField.getName().compareTo(rightField.getName()); + } + + if (leftColumn != null && rightColumn != null) { + return CassandraColumnAnnotationComparator.IT.compare(leftColumn, rightColumn); + } + + if (leftColumn != null && rightColumn == null) { + return leftColumn.value().compareTo(rightField.getName()); + } + + // else leftColumn == null && rightColumn != null) + return leftField.getName().compareTo(rightColumn.value()); + } +} \ No newline at end of file diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPrimaryKeyColumnComparator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPrimaryKeyColumnAnnotationComparator.java similarity index 69% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPrimaryKeyColumnComparator.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPrimaryKeyColumnAnnotationComparator.java index db70c908e..96a46f9d4 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPrimaryKeyColumnComparator.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraPrimaryKeyColumnAnnotationComparator.java @@ -19,7 +19,7 @@ import org.springframework.cassandra.core.PrimaryKeyType; * * @author Matthew T. Adams */ -public enum DefaultCassandraPrimaryKeyColumnComparator implements Comparator { +public enum CassandraPrimaryKeyColumnAnnotationComparator implements Comparator { /** * The sole instance of this class. @@ -27,23 +27,23 @@ public enum DefaultCassandraPrimaryKeyColumnComparator implements Comparator CassandraPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity(typeInformation); + CassandraPersistentEntity entity = new CachingCassandraPersistentEntity(typeInformation, this); if (context != null) { entity.setApplicationContext(context); @@ -104,9 +106,7 @@ public class DefaultCassandraMappingContext extends Assert.notNull(entity); - final CreateTableSpecification spec = new CreateTableSpecification(); - - spec.name(entity.getTableName()); + final CreateTableSpecification spec = createTable().name(entity.getTableName()); entity.doWithProperties(new PropertyHandler() { @@ -124,7 +124,7 @@ public class DefaultCassandraMappingContext extends if (pkProp.isPartitionKeyColumn()) { spec.partitionKeyColumn(pkProp.getColumnName(), pkProp.getDataType()); - } else { + } else { // it's a cluster column spec.clusteredKeyColumn(pkProp.getColumnName(), pkProp.getDataType(), pkProp.getPrimaryKeyOrdering()); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPersistentPropertyColumnComparator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPersistentPropertyColumnComparator.java deleted file mode 100644 index 8d83318fc..000000000 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraPersistentPropertyColumnComparator.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.springframework.data.cassandra.mapping; - -import java.util.Comparator; - -/** - * {@link Comparator} implementation that uses the {@link CassandraPersistentProperty}'s column name for ordering. - * - * @author Alex Shvid - * @author Matthew T. Adams - */ -public enum DefaultCassandraPersistentPropertyColumnComparator implements Comparator { - - /** - * The sole instance of this class. - */ - IT; - - @Override - public int compare(CassandraPersistentProperty o1, CassandraPersistentProperty o2) { - return o1.getColumnName().compareTo(o2.getColumnName()); - } -} \ No newline at end of file diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/EntityMapping.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/EntityMapping.java similarity index 72% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/EntityMapping.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/EntityMapping.java index 9431751b9..0894f42e8 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/EntityMapping.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/EntityMapping.java @@ -1,4 +1,7 @@ -package org.springframework.data.cassandra.config; +package org.springframework.data.cassandra.mapping; + +import java.util.HashMap; +import java.util.Map; /** * Mapping information for an individual entity class. @@ -7,9 +10,21 @@ package org.springframework.data.cassandra.config; */ public class EntityMapping { + /** + * The name of the entity's class. + */ protected String entityClassName; + + /** + * The name of the table to which the entity is mapped. + */ protected String tableName; + /** + * The {@link PropertyMapping}s for each persistent property, keyed on property name. + */ + protected Map propertyMappings = new HashMap(); + public EntityMapping(String entityClassName, String tableName) { setEntityClassName(entityClassName); setTableName(tableName); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/Mapping.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Mapping.java similarity index 95% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/Mapping.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Mapping.java index 87b9f5254..bb0779185 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/Mapping.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Mapping.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.config; +package org.springframework.data.cassandra.mapping; import java.util.Collections; import java.util.HashSet; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PropertyMapping.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PropertyMapping.java new file mode 100644 index 000000000..a47c79c3d --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PropertyMapping.java @@ -0,0 +1,38 @@ +package org.springframework.data.cassandra.mapping; + +import org.springframework.util.Assert; + +/** + * Mapping between a persistent entity's property and its column. + * + * @author Matthew T. Adams + */ +public class PropertyMapping { + + protected String propertyName; + protected String columnName; + + public PropertyMapping(String propertyName, String columnName) { + + setPropertyName(propertyName); + setColumnName(columnName); + } + + public String getPropertyName() { + return propertyName; + } + + protected void setPropertyName(String propertyName) { + Assert.notNull(propertyName); + this.propertyName = propertyName; + } + + public String getColumnName() { + return columnName; + } + + protected void setColumnName(String columnName) { + Assert.notNull(columnName); + this.columnName = columnName; + } +} 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 e8ed41547..5b5c97005 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 @@ -16,7 +16,6 @@ package org.springframework.data.cassandra.repository; import java.io.Serializable; -import java.util.List; import org.springframework.data.repository.CrudRepository; @@ -24,9 +23,7 @@ import org.springframework.data.repository.CrudRepository; * Cassandra-specific extension of the {@link CrudRepository} interface. * * @author Alex Shvid + * @author Matthew T. Adams */ public interface CassandraRepository extends CrudRepository { - - List findByPartitionKey(ID id); - } 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 528defa6d..bc0c37de1 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 @@ -33,12 +33,4 @@ public interface CassandraEntityInformation extends * @return */ String getTableName(); - - /** - * Returns the column that the id will be persisted to. - * - * @return - */ - String getIdColumn(); - } 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 c3385ad4e..5e53f5fa9 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 @@ -59,10 +59,6 @@ public class MappingCassandraEntityInformation exten this.customTableName = customTableName; } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.support.EntityInformation#getId(java.lang.Object) - */ @SuppressWarnings("unchecked") @Override public ID getId(T entity) { @@ -80,28 +76,14 @@ public class MappingCassandraEntityInformation exten } } - /* (non-Javadoc) - * @see org.springframework.data.repository.support.EntityInformation#getIdType() - */ @SuppressWarnings("unchecked") @Override public Class getIdType() { return (Class) entityMetadata.getIdProperty().getType(); } - /* (non-Javadoc) - * @see org.springframework.data.mongodb.repository.CassandraEntityInformation#getTableName() - */ @Override public String getTableName() { return customTableName == null ? entityMetadata.getTableName() : customTableName; } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.repository.CassandraEntityInformation#getIdColumn() - */ - public String getIdColumn() { - return entityMetadata.getIdProperty().getName(); - } - } 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 fb2269b2d..da529aa6e 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 @@ -31,6 +31,7 @@ import com.datastax.driver.core.querybuilder.Clause; import com.datastax.driver.core.querybuilder.Delete; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; +import com.datastax.driver.core.querybuilder.Select.Where; /** * Repository base implementation for Cassandra. @@ -41,8 +42,8 @@ import com.datastax.driver.core.querybuilder.Select; public class SimpleCassandraRepository implements CassandraRepository { - private final CassandraTemplate cassandraTemplate; - private final CassandraEntityInformation entityInformation; + protected final CassandraTemplate cassandraTemplate; + protected final CassandraEntityInformation entityInformation; /** * Creates a new {@link SimpleCassandraRepository} for the given {@link CassandraEntityInformation} and @@ -51,8 +52,7 @@ public class SimpleCassandraRepository implements Ca * @param metadata must not be {@literal null}. * @param template must not be {@literal null}. */ - public SimpleCassandraRepository(CassandraEntityInformation metadata, - CassandraTemplate cassandraTemplate) { + public SimpleCassandraRepository(CassandraEntityInformation metadata, CassandraTemplate cassandraTemplate) { Assert.notNull(cassandraTemplate); Assert.notNull(metadata); @@ -65,6 +65,7 @@ public class SimpleCassandraRepository implements Ca * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object) */ + @Override public S save(S entity) { Assert.notNull(entity, "Entity must not be null!"); @@ -76,6 +77,7 @@ public class SimpleCassandraRepository implements Ca * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable) */ + @Override public List save(Iterable entities) { Assert.notNull(entities, "The given Iterable of entities not be null!"); @@ -90,78 +92,31 @@ public class SimpleCassandraRepository implements Ca return result; } - private Clause getIdClause(ID id) { - Clause clause = QueryBuilder.eq(entityInformation.getIdColumn(), id); - return clause; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable) - */ - public T findOne(ID id) { - Assert.notNull(id, "The given id must not be null!"); - - Select select = QueryBuilder.select().all().from(entityInformation.getTableName()); - select.where(getIdClause(id)); - - return cassandraTemplate.selectOne(select, entityInformation.getJavaType()); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.cassandra.repository.CassandraRepository#findByPartitionKey(java.io.Serializable) - */ @Override - public List findByPartitionKey(ID id) { - Assert.notNull(id, "The given id must not be null!"); - - Select select = QueryBuilder.select().all().from(entityInformation.getTableName()); - select.where(getIdClause(id)); - - return cassandraTemplate.select(select, entityInformation.getJavaType()); + public T findOne(ID id) { + return cassandraTemplate.selectOneById(entityInformation.getJavaType(), id); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable) - */ + @Override public boolean exists(ID id) { - - Assert.notNull(id, "The given id must not be null!"); - - Select select = QueryBuilder.select().countAll().from(entityInformation.getTableName()); - select.where(getIdClause(id)); - - Long num = cassandraTemplate.count(select); - return num != null && num.longValue() > 0; + return cassandraTemplate.countById(entityInformation.getJavaType(), id) >= 1; // TODO: == instead of >= ? } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#count() - */ + @Override public long count() { return cassandraTemplate.count(entityInformation.getTableName()); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable) - */ + @Override public void delete(ID id) { - Assert.notNull(id, "The given id must not be null!"); - - Delete delete = QueryBuilder.delete().all().from(entityInformation.getTableName()); - delete.where(getIdClause(id)); - - cassandraTemplate.execute(delete.getQueryString()); + cassandraTemplate.deleteById(entityInformation.getJavaType(), id); } /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object) */ + @Override public void delete(T entity) { Assert.notNull(entity, "The given entity must not be null!"); delete(entityInformation.getId(entity)); @@ -171,6 +126,7 @@ public class SimpleCassandraRepository implements Ca * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable) */ + @Override public void delete(Iterable entities) { Assert.notNull(entities, "The given Iterable of entities not be null!"); @@ -184,6 +140,7 @@ public class SimpleCassandraRepository implements Ca * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#deleteAll() */ + @Override public void deleteAll() { cassandraTemplate.truncate(entityInformation.getTableName()); } @@ -192,29 +149,19 @@ public class SimpleCassandraRepository implements Ca * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#findAll() */ + @Override public List findAll() { Select select = QueryBuilder.select().all().from(entityInformation.getTableName()); return findAll(select); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable) - */ + @Override public Iterable findAll(Iterable ids) { - List parameters = new ArrayList(); - for (ID id : ids) { - parameters.add(id); - } - Clause clause = QueryBuilder.in(entityInformation.getIdColumn(), parameters.toArray()); - Select select = QueryBuilder.select().all().from(entityInformation.getTableName()); - select.where(clause); - - return findAll(select); + return cassandraTemplate.selectByIds(entityInformation.getJavaType(), ids); } - private List findAll(Select query) { + protected List findAll(Select query) { if (query == null) { return Collections.emptyList(); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java index 5b51d6f35..3aa98506e 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/util/CqlUtils.java @@ -3,25 +3,14 @@ package org.springframework.data.cassandra.util; import java.util.ArrayList; import java.util.List; -import org.springframework.cassandra.core.CqlTemplate; -import org.springframework.cassandra.core.QueryOptions; -import org.springframework.data.cassandra.exception.EntityWriterException; +import org.springframework.cassandra.core.cql.CqlStringUtils; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; -import org.springframework.data.convert.EntityWriter; import org.springframework.data.mapping.PropertyHandler; import com.datastax.driver.core.ColumnMetadata; import com.datastax.driver.core.DataType; -import com.datastax.driver.core.Query; -import com.datastax.driver.core.Statement; import com.datastax.driver.core.TableMetadata; -import com.datastax.driver.core.querybuilder.Batch; -import com.datastax.driver.core.querybuilder.Delete; -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; /** * Utilities to convert Cassandra Annotated objects to Queries and CQL. @@ -33,39 +22,7 @@ import com.datastax.driver.core.querybuilder.Update; public abstract class CqlUtils { /** - * Create the List of CQL for the indexes required for Cassandra mapped Table. - * - * @param tableName - * @param entity - * @return The list of CQL statements to run with session.execute() - */ - public static List createIndexes(final String tableName, final CassandraPersistentEntity entity) { - final List result = new ArrayList(); - - entity.doWithProperties(new PropertyHandler() { - @Override - public void doWithPersistentProperty(CassandraPersistentProperty prop) { - - if (prop.isIndexed()) { - - final StringBuilder str = new StringBuilder(); - str.append("CREATE INDEX ON "); - str.append(tableName); - str.append(" ("); - str.append(prop.getColumnName()); - str.append(");"); - - result.add(str.toString()); - } - - } - }); - - return result; - } - - /** - * Alter the table to refelct the entity annotations + * Alter the table to reflect the entity annotations * * @param tableName * @param entity @@ -104,7 +61,7 @@ public abstract class CqlUtils { str.append("TYPE "); } - str.append(toCQL(columnDataType)); + str.append(CqlStringUtils.toCql(columnDataType)); str.append(';'); result.add(str.toString()); @@ -114,243 +71,4 @@ public abstract class CqlUtils { return result; } - - /** - * Generates a Query Object for an insert - * - * @param tableName - * @param objectToSave - * @param entity - * @param optionsByName - * - * @return The Query object to run with session.execute(); - * @throws EntityWriterException - */ - public static Query toInsertQuery(String tableName, final Object objectToSave, QueryOptions options, - EntityWriter entityWriter) throws EntityWriterException { - - final Insert q = QueryBuilder.insertInto(tableName); - - /* - * Write properties - */ - entityWriter.write(objectToSave, q); - - /* - * Add Query Options - */ - CqlTemplate.addQueryOptions(q, options); - - /* - * Add TTL to Insert object - */ - if (options != null && options.getTtl() != null) { - q.using(QueryBuilder.ttl(options.getTtl())); - } - - return q; - - } - - /** - * Generates a Query Object for an Update - * - * @param tableName - * @param objectToSave - * @param entity - * @param optionsByName - * - * @return The Query object to run with session.execute(); - * @throws EntityWriterException - */ - public static Query toUpdateQuery(String tableName, final Object objectToSave, QueryOptions options, - EntityWriter entityWriter) throws EntityWriterException { - - final Update q = QueryBuilder.update(tableName); - - /* - * Write properties - */ - entityWriter.write(objectToSave, q); - - /* - * Add Query Options - */ - CqlTemplate.addQueryOptions(q, options); - - /* - * Add TTL to Insert object - */ - if (options != null && options.getTtl() != null) { - q.using(QueryBuilder.ttl(options.getTtl())); - } - - return q; - - } - - /** - * Generates a Batch Object for multiple Updates - * - * @param tableName - * @param objectsToSave - * @param entity - * @param optionsByName - * - * @return The Query object to run with session.execute(); - * @throws EntityWriterException - */ - public static Batch toUpdateBatchQuery(final String tableName, final List objectsToSave, QueryOptions options, - EntityWriter entityWriter) throws EntityWriterException { - - /* - * Return variable is a Batch statement - */ - final Batch b = QueryBuilder.batch(); - - for (final T objectToSave : objectsToSave) { - - b.add((Statement) toUpdateQuery(tableName, objectToSave, options, entityWriter)); - - } - - /* - * Add Query Options - */ - CqlTemplate.addQueryOptions(b, options); - - return b; - - } - - /** - * Generates a Batch Object for multiple inserts - * - * @param tableName - * @param objectsToSave - * @param entity - * @param optionsByName - * - * @return The Query object to run with session.execute(); - * @throws EntityWriterException - */ - public static Batch toInsertBatchQuery(final String tableName, final List objectsToSave, QueryOptions options, - EntityWriter entityWriter) throws EntityWriterException { - - /* - * Return variable is a Batch statement - */ - final Batch b = QueryBuilder.batch(); - - for (final T objectToSave : objectsToSave) { - - b.add((Statement) toInsertQuery(tableName, objectToSave, options, entityWriter)); - - } - - /* - * Add Query Options - */ - CqlTemplate.addQueryOptions(b, options); - - return b; - - } - - /** - * Create a Delete Query Object from an annotated POJO - * - * @param tableName - * @param objectToRemove - * @param entity - * @param optionsByName - * @return - * @throws EntityWriterException - */ - public static Query toDeleteQuery(String tableName, final Object objectToRemove, QueryOptions options, - EntityWriter entityWriter) throws EntityWriterException { - - final Delete.Selection ds = QueryBuilder.delete(); - final Delete q = ds.from(tableName); - final Where w = q.where(); - - /* - * Write where condition to find by Id - */ - entityWriter.write(objectToRemove, w); - - CqlTemplate.addQueryOptions(q, options); - - return q; - - } - - /** - * @param dataType - * @return - */ - public static String toCQL(DataType dataType) { - if (dataType.getTypeArguments().isEmpty()) { - return dataType.getName().name(); - } else { - StringBuilder str = new StringBuilder(); - str.append(dataType.getName().name()); - str.append('<'); - for (DataType argDataType : dataType.getTypeArguments()) { - if (str.charAt(str.length() - 1) != '<') { - str.append(','); - } - str.append(argDataType.getName().name()); - } - str.append('>'); - return str.toString(); - } - } - - /** - * @param tableName - * @return - */ - public static String dropTable(String tableName) { - - if (tableName == null) { - return null; - } - - StringBuilder str = new StringBuilder(); - str.append("DROP TABLE " + tableName + ";"); - return str.toString(); - } - - /** - * Create a Batch Query object for multiple deletes. - * - * @param tableName - * @param entities - * @param entity - * @param optionsByName - * - * @return - * @throws EntityWriterException - */ - public static Batch toDeleteBatchQuery(String tableName, List entities, QueryOptions options, - EntityWriter entityWriter) throws EntityWriterException { - - /* - * Return variable is a Batch statement - */ - final Batch b = QueryBuilder.batch(); - - for (final T objectToSave : entities) { - - b.add((Statement) toDeleteQuery(tableName, objectToSave, options, entityWriter)); - - } - - CqlTemplate.addQueryOptions(b, options); - - return b; - - } - } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/CassandraCompositePrimaryKeyIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/CassandraCompositePrimaryKeyIntegrationTests.java new file mode 100644 index 000000000..32ca22f1c --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/CassandraCompositePrimaryKeyIntegrationTests.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.mapping; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.cassandra.core.PrimaryKeyType; +import org.springframework.cassandra.core.keyspace.ColumnSpecification; +import org.springframework.cassandra.core.keyspace.CreateTableSpecification; +import org.springframework.data.cassandra.mapping.BasicCassandraPersistentProperty; +import org.springframework.data.cassandra.mapping.CachingCassandraPersistentProperty; +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.mapping.CassandraSimpleTypeHolder; +import org.springframework.data.cassandra.mapping.Column; +import org.springframework.data.cassandra.mapping.DefaultCassandraMappingContext; +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.util.ClassTypeInformation; +import org.springframework.util.ReflectionUtils; + +import com.datastax.driver.core.DataType; + +/** + * Integration test for {@link BasicCassandraPersistentProperty} with a composite primary key class. + * + * @author Matthew T. Adams + */ +public class CassandraCompositePrimaryKeyIntegrationTests { + + private static final CassandraSimpleTypeHolder SIMPLE_TYPE_HOLDER = new CassandraSimpleTypeHolder(); + + @PrimaryKeyClass + static class Key { + + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) + String z; + + @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.CLUSTERED) + String a; + } + + @Table + static class Thing { + + @PrimaryKey + Key id; + + Date time; + + @Column("message") + String text; + } + + CassandraMappingContext context; + CassandraPersistentEntity thing; + CassandraPersistentEntity key; + + @Before + public void setup() { + context = new DefaultCassandraMappingContext(); + thing = context.getPersistentEntity(ClassTypeInformation.from(Thing.class)); + key = context.getPersistentEntity(ClassTypeInformation.from(Key.class)); + } + + @Test + public void validateMappingInfo() { + + Field field = ReflectionUtils.findField(Thing.class, "id"); + CassandraPersistentProperty property = new CachingCassandraPersistentProperty(field, null, thing, + SIMPLE_TYPE_HOLDER); + assertTrue(property.isIdProperty()); + assertTrue(property.isCompositePrimaryKey()); + + List expectedColumnNames = Arrays.asList(new String[] { "z", "a" }); + assertTrue(expectedColumnNames.equals(property.getColumnNames())); + + List actualColumnNames = new ArrayList(); + List properties = property.getCompositePrimaryKeyProperties(); + for (CassandraPersistentProperty p : properties) { + actualColumnNames.addAll(p.getColumnNames()); + } + assertTrue(expectedColumnNames.equals(actualColumnNames)); + + CreateTableSpecification spec = context.getCreateTableSpecificationFor(thing); + + List partitionKeyColumns = spec.getPartitionKeyColumns(); + assertEquals(1, partitionKeyColumns.size()); + ColumnSpecification partitionKeyColumn = partitionKeyColumns.get(0); + assertEquals("z", partitionKeyColumn.getName()); + assertEquals(PrimaryKeyType.PARTITIONED, partitionKeyColumn.getKeyType()); + assertEquals(DataType.text(), partitionKeyColumn.getType()); + + List clusteredKeyColumns = spec.getClusteredKeyColumns(); + assertEquals(1, clusteredKeyColumns.size()); + ColumnSpecification clusteredKeyColumn = clusteredKeyColumns.get(0); + assertEquals("a", clusteredKeyColumn.getName()); + assertEquals(PrimaryKeyType.CLUSTERED, clusteredKeyColumn.getKeyType()); + assertEquals(DataType.text(), partitionKeyColumn.getType()); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/CompoundPrimaryKeyIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/CompoundPrimaryKeyIntegrationTests.java index 60601573c..7b868fb0d 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/CompoundPrimaryKeyIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/CompoundPrimaryKeyIntegrationTests.java @@ -24,6 +24,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntity; import org.springframework.data.cassandra.mapping.BasicCassandraPersistentProperty; +import org.springframework.data.cassandra.mapping.CachingCassandraPersistentProperty; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; import org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder; @@ -76,6 +77,6 @@ public class CompoundPrimaryKeyIntegrationTests { } private CassandraPersistentProperty getPropertyFor(Field field) { - return new BasicCassandraPersistentProperty(field, null, entity, new CassandraSimpleTypeHolder()); + return new CachingCassandraPersistentProperty(field, null, entity, new CassandraSimpleTypeHolder()); } }