diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/AsynchronousQueryListener.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/AsynchronousQueryListener.java index 3c59206b4..7cf4a587f 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/AsynchronousQueryListener.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/AsynchronousQueryListener.java @@ -18,16 +18,17 @@ package org.springframework.cassandra.core; import com.datastax.driver.core.ResultSetFuture; /** - * @author David Webb + * Interface used to give an implementation access to a {@link ResultSetFuture} after the query has completed. * + * @author David Webb */ public interface AsynchronousQueryListener { /** * Called upon Query Completion. * - * @param rsf The given ResultSetFuture's get methods should return immediately. + * @param rsf The {@link ResultSetFuture}. Since this isn't called until the asynchronous query completes, it can be + * immediately interrogated. */ public void onQueryComplete(ResultSetFuture rsf); - } 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..361acda8c 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,8 +21,17 @@ 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.Query; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.ResultSetFuture; import com.datastax.driver.core.Session; @@ -52,12 +61,26 @@ public interface CqlOperations { void execute(String cql) throws DataAccessException; /** - * Executes the supplied CQL Query Asynchronously and returns nothing. + * Executes the supplied Query and returns nothing. * - * @param cql The CQL Statement to execute + * @param query The {@link Query} to execute + */ + void execute(Query query) throws DataAccessException; + + /** + * Executes the supplied Query Asynchronously and returns nothing. + * + * @param cql The {@link Query} to execute */ void executeAsynchronously(String cql) throws DataAccessException; + /** + * Executes the supplied CQL Query Asynchronously and returns nothing. + * + * @param query The {@link Query} to execute + */ + void executeAsynchronously(Query query) throws DataAccessException; + /** * Executes the provided CQL Query, and extracts the results with the ResultSetExtractor. This uses default Query * Options when extracting the ResultSet. @@ -100,10 +123,10 @@ public interface CqlOperations { ResultSetFuture queryAsynchronously(String cql, QueryOptions options); /** - * Executes the provided CQL Query with the provided Runnable implementations. + * Executes the provided CQL Query with the provided {@link Runnable} implementation. * * @param cql The Query - * @param listener Runnable Listener for handling the query in a separate thread + * @param listener {@link Runnable} listener for handling the query in a separate thread */ void queryAsynchronously(String cql, Runnable listener); @@ -113,7 +136,8 @@ public interface CqlOperations { * query is completed for optimal flexibility. * * @param cql The Query - * @param listener Runnable Listener for handling the query in a separate thread + * @param listener {@link AsynchronousQueryListener} Listener for handling the query's {@link ResultSetFuture} in a + * separate thread */ void queryAsynchronously(String cql, AsynchronousQueryListener listener); @@ -181,6 +205,23 @@ public interface CqlOperations { */ void queryAsynchronously(String cql, AsynchronousQueryListener listener, QueryOptions options, Executor executor); + /** + * Executes the provided CQL query and returns the {@link ResultSet}. + * + * @param cql The query + * @return The {@link ResultSet} + */ + ResultSet query(String cql); + + /** + * Executes the provided CQL query with the given {@link QueryOptions} and returns the {@link ResultSet}. + * + * @param cql The query + * @param options The {@link QueryOptions}; may be null. + * @return The {@link ResultSet} + */ + ResultSet query(String cql, QueryOptions options); + /** * Executes the provided CQL Query, and extracts the results with the ResultSetExtractor. * @@ -754,4 +795,67 @@ public interface CqlOperations { */ void truncate(String tableName); + /** + * Counts all rows for given table + * + * @param tableName + * @return + */ + long count(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..a01d1aec9 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,8 +27,25 @@ 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.InvalidDataAccessApiUsageException; import org.springframework.dao.QueryTimeoutException; import org.springframework.util.Assert; @@ -87,6 +104,11 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { return doExecute(sessionCallback); } + @Override + public void execute(Query query) throws DataAccessException { + doExecute(query, null); + } + @Override public void execute(final String cql) throws DataAccessException { doExecute(cql, null); @@ -278,6 +300,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 +310,49 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { return process(doExecute(cql, options), rowMapper); } + @Override + public ResultSet query(String cql) { + return query(cql, (QueryOptions) null); + } + + @Override + public ResultSet query(String cql, QueryOptions options) { + + return query(cql, new ResultSetExtractor() { + + @Override + public ResultSet extractData(ResultSet rs) throws DriverException, DataAccessException { + return rs; + } + }, options); + } + + @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); } @@ -357,14 +403,14 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { * @param callback * @return */ - protected ResultSet doExecute(final BoundStatement bs, final QueryOptions options) { + protected ResultSet doExecute(final Query q, final QueryOptions options) { return doExecute(new SessionCallback() { @Override public ResultSet doInSession(Session s) throws DataAccessException { - addQueryOptions(bs, options); - return s.execute(bs); + addQueryOptions(q, options); + return s.execute(q); } }); } @@ -411,7 +457,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { * * @return */ - private Set getHosts() { + protected Set getHosts() { /* * Get the cluster metadata for this session @@ -450,6 +496,16 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { }); } + @Override + public void executeAsynchronously(final Query query) throws DataAccessException { + execute(new SessionCallback() { + @Override + public Object doInSession(Session s) throws DataAccessException { + return s.executeAsync(query); + } + }); + } + @Override public void process(ResultSet resultSet, RowCallbackHandler rch) throws DataAccessException { try { @@ -766,6 +822,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 +851,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 +880,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 +902,121 @@ 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)); + } + }); + } + + @Override + public long count(String tableName) { + return selectCount(QueryBuilder.select().countAll().from(tableName).getQueryString()); + } + + protected long selectCount(String countQuery) { + + return query(countQuery, new ResultSetExtractor() { + + @Override + public Long extractData(ResultSet rs) throws DriverException, DataAccessException { + + Row row = rs.one(); + if (row == null) { + throw new InvalidDataAccessApiUsageException(String.format("count query did not return any results")); + } + + return row.getLong(0); + } + }); + } } \ 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 27543b8cc..c52a9d914 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 @@ -32,10 +32,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/core/util/CollectionUtils.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/util/CollectionUtils.java new file mode 100644 index 000000000..e9ceb2b9a --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/util/CollectionUtils.java @@ -0,0 +1,27 @@ +package org.springframework.cassandra.core.util; + +import java.util.ArrayList; +import java.util.List; + +public class CollectionUtils { + + @SuppressWarnings("unchecked") + public static T[] toArray(Iterable i) { + return (T[]) toList(i).toArray(); + } + + public static List toList(Iterable i) { + + List list = null; + if (i instanceof List) { + list = (List) i; + } else { + list = new ArrayList(); + for (T t : i) { + list.add(t); + } + } + + return list; + } +} 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/pom.xml b/spring-data-cassandra/pom.xml index 01c29f55c..389d94e2b 100644 --- a/spring-data-cassandra/pom.xml +++ b/spring-data-cassandra/pom.xml @@ -127,6 +127,12 @@ test + + commons-io + commons-io + 2.4 + test + 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..5a505d5c4 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; @@ -66,7 +68,7 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC * The base package to scan for entities annotated with {@link Table} annotations. By default, returns the package * name of {@literal this} (this.getClass().getPackage().getName()). */ - public String getMappingBasePackage() { + public String getEntityBasePackage() { return getClass().getPackage().getName(); } @@ -125,13 +127,13 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC /** * Scans the mapping base package for entity classes annotated with {@link Table} or {@link Persistent}. * - * @see #getMappingBasePackage() + * @see #getEntityBasePackage() * @return Set<Class<?>> representing the annotated entity classes found. * @throws ClassNotFoundException */ protected Set> getInitialEntitySet() throws ClassNotFoundException { - String basePackage = getMappingBasePackage(); + String basePackage = getEntityBasePackage(); Set> initialEntitySet = new HashSet>(); if (StringUtils.hasText(basePackage)) { @@ -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..ec64f8c90 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,11 +31,10 @@ 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; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import com.datastax.driver.core.Row; @@ -54,14 +53,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}. @@ -69,7 +68,11 @@ public class MappingCassandraConverter extends AbstractCassandraConverter implem * @param mappingContext must not be {@literal null}. */ public MappingCassandraConverter(CassandraMappingContext mappingContext) { + super(new DefaultConversionService()); + + Assert.notNull(mappingContext); + this.mappingContext = mappingContext; this.spELContext = new SpELContext(RowReaderPropertyAccessor.INSTANCE); } @@ -111,45 +114,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 +180,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..c7641f026 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 @@ -125,24 +124,4 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand } }); } - - /** - * @param entityClass - * @return - */ - @Override - public String determineTableName(Class entityClass) { - - if (entityClass == null) { - throw new InvalidDataAccessApiUsageException( - "No class parameter provided, entity table name can't be determined!"); - } - - CassandraPersistentEntity entity = getCassandraMappingContext().getPersistentEntity(entityClass); - if (entity == null) { - throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " - + entityClass.getName()); - } - return entity.getTableName(); - } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReadRowCallback.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraConverterRowCallback.java similarity index 68% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReadRowCallback.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraConverterRowCallback.java index a87050ebb..8b02200de 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReadRowCallback.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraConverterRowCallback.java @@ -16,32 +16,34 @@ package org.springframework.data.cassandra.core; import org.springframework.cassandra.core.RowCallback; -import org.springframework.data.convert.EntityReader; +import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.util.Assert; import com.datastax.driver.core.Row; /** - * Simple {@link RowCallback} that will transform {@link Row} into the given target type using the given - * {@link EntityReader}. + * Simple {@link RowCallback} that will transform a {@link Row} into the given target type using the given + * {@link CassandraConverter}. * * @author Alex Shvid + * @author Matthew T. Adams */ -public class ReadRowCallback implements RowCallback { +public class CassandraConverterRowCallback implements RowCallback { - private final EntityReader reader; + private final CassandraConverter reader; private final Class type; - public ReadRowCallback(EntityReader reader, Class type) { + public CassandraConverterRowCallback(CassandraConverter reader, Class type) { + Assert.notNull(reader); Assert.notNull(type); + this.reader = reader; this.type = type; } @Override public T doWith(Row row) { - T source = reader.read(type, row); - return source; + return reader.read(type, row); } } \ No newline at end of file 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..034982f87 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 @@ -21,8 +21,6 @@ import org.springframework.cassandra.core.CqlOperations; import org.springframework.cassandra.core.QueryOptions; import org.springframework.data.cassandra.convert.CassandraConverter; -import com.datastax.driver.core.querybuilder.Select; - /** * Operations for interacting with Cassandra. These operations are used by the Repository implementation, but can also * be used directly when that is desired by the developer. @@ -30,7 +28,6 @@ import com.datastax.driver.core.querybuilder.Select; * @author Alex Shvid * @author David Webb * @author Matthew Adams - * */ public interface CassandraOperations extends CqlOperations { @@ -46,49 +43,25 @@ public interface CassandraOperations extends CqlOperations { * Execute query and convert ResultSet to the list of entities * * @param query must not be {@literal null}. - * @param selectClass must not be {@literal null}, mapped entity type. + * @param type must not be {@literal null}, mapped entity type. * @return */ - List select(String cql, Class selectClass); + List select(String cql, Class type); - /** - * Execute query and convert ResultSet to the list of entities - * - * @param selectQuery must not be {@literal null}. - * @param selectClass must not be {@literal null}, mapped entity type. - * @return - */ - - List select(Select selectQuery, Class selectClass); + T selectOneById(Class type, Object id); /** * Execute query and convert ResultSet to the entity * * @param query must not be {@literal null}. - * @param selectClass must not be {@literal null}, mapped entity type. + * @param type must not be {@literal null}, mapped entity type. * @return */ - T selectOne(String cql, Class selectClass); + T selectOne(String cql, Class type); - T selectOne(Select selectQuery, Class selectClass); + boolean exists(Class type, Object id); - /** - * Counts rows for given query - * - * @param selectQuery - * @return - */ - - Long count(Select selectQuery); - - /** - * Counts all rows for given table - * - * @param tableName - * @return - */ - - Long count(String tableName); + long count(Class type); /** * Insert the given object to the table by id. @@ -97,23 +70,6 @@ public interface CassandraOperations extends CqlOperations { */ T insert(T entity); - /** - * Insert the given object to the table by id. - * - * @param entity - * @param tableName - * @return - */ - T insert(T entity, String tableName); - - /** - * @param entity - * @param tableName - * @param options - * @return - */ - T insert(T entity, String tableName, QueryOptions options); - /** * @param entity * @param tableName @@ -130,15 +86,6 @@ public interface CassandraOperations extends CqlOperations { */ List insert(List entities); - /** - * Insert the given list of objects to the table by name. - * - * @param entities - * @param tableName - * @return - */ - List insert(List entities, String tableName); - /** * @param entities * @param tableName @@ -147,14 +94,6 @@ public interface CassandraOperations extends CqlOperations { */ List insert(List entities, QueryOptions options); - /** - * @param entities - * @param tableName - * @param options - * @return - */ - List insert(List entities, String tableName, QueryOptions options); - /** * Insert the given object to the table by id. * @@ -162,13 +101,6 @@ public interface CassandraOperations extends CqlOperations { */ T insertAsynchronously(T entity); - /** - * Insert the given object to the table by id. - * - * @param object - */ - T insertAsynchronously(T entity, String tableName); - /** * @param entity * @param tableName @@ -177,14 +109,6 @@ public interface CassandraOperations extends CqlOperations { */ T insertAsynchronously(T entity, QueryOptions options); - /** - * @param entity - * @param tableName - * @param options - * @return - */ - T insertAsynchronously(T entity, String tableName, QueryOptions options); - /** * Insert the given object to the table by id. * @@ -192,13 +116,6 @@ public interface CassandraOperations extends CqlOperations { */ List insertAsynchronously(List entities); - /** - * Insert the given object to the table by id. - * - * @param object - */ - List insertAsynchronously(List entities, String tableName); - /** * @param entities * @param tableName @@ -207,14 +124,6 @@ public interface CassandraOperations extends CqlOperations { */ List insertAsynchronously(List entities, QueryOptions options); - /** - * @param entities - * @param tableName - * @param options - * @return - */ - List insertAsynchronously(List entities, String tableName, QueryOptions options); - /** * Insert the given object to the table by id. * @@ -222,13 +131,6 @@ public interface CassandraOperations extends CqlOperations { */ T update(T entity); - /** - * Insert the given object to the table by id. - * - * @param object - */ - T update(T entity, String tableName); - /** * @param entity * @param tableName @@ -237,14 +139,6 @@ public interface CassandraOperations extends CqlOperations { */ T update(T entity, QueryOptions options); - /** - * @param entity - * @param tableName - * @param options - * @return - */ - T update(T entity, String tableName, QueryOptions options); - /** * Insert the given object to the table by id. * @@ -252,13 +146,6 @@ public interface CassandraOperations extends CqlOperations { */ List update(List entities); - /** - * Insert the given object to the table by id. - * - * @param object - */ - List update(List entities, String tableName); - /** * @param entities * @param tableName @@ -267,14 +154,6 @@ public interface CassandraOperations extends CqlOperations { */ List update(List entities, QueryOptions options); - /** - * @param entities - * @param tableName - * @param options - * @return - */ - List update(List entities, String tableName, QueryOptions options); - /** * Insert the given object to the table by id. * @@ -282,13 +161,6 @@ public interface CassandraOperations extends CqlOperations { */ T updateAsynchronously(T entity); - /** - * Insert the given object to the table by id. - * - * @param object - */ - T updateAsynchronously(T entity, String tableName); - /** * @param entity * @param tableName @@ -297,14 +169,6 @@ public interface CassandraOperations extends CqlOperations { */ T updateAsynchronously(T entity, QueryOptions options); - /** - * @param entity - * @param tableName - * @param options - * @return - */ - T updateAsynchronously(T entity, String tableName, QueryOptions options); - /** * Insert the given object to the table by id. * @@ -312,13 +176,6 @@ public interface CassandraOperations extends CqlOperations { */ List updateAsynchronously(List entities); - /** - * Insert the given object to the table by id. - * - * @param object - */ - List updateAsynchronously(List entities, String tableName); - /** * @param entities * @param tableName @@ -327,14 +184,6 @@ public interface CassandraOperations extends CqlOperations { */ List updateAsynchronously(List entities, QueryOptions options); - /** - * @param entities - * @param tableName - * @param options - * @return - */ - List updateAsynchronously(List entities, String tableName, QueryOptions options); - /** * Remove the given object from the table by id. * @@ -342,14 +191,6 @@ public interface CassandraOperations extends CqlOperations { */ void delete(T entity); - /** - * Removes the given object from the given table. - * - * @param object - * @param table must not be {@literal null} or empty. - */ - void delete(T entity, String tableName); - /** * @param entity * @param tableName @@ -357,13 +198,6 @@ public interface CassandraOperations extends CqlOperations { */ void delete(T entity, QueryOptions options); - /** - * @param entity - * @param tableName - * @param options - */ - void delete(T entity, String tableName, QueryOptions options); - /** * Remove the given object from the table by id. * @@ -371,14 +205,6 @@ public interface CassandraOperations extends CqlOperations { */ void delete(List entities); - /** - * Removes the given object from the given table. - * - * @param object - * @param table must not be {@literal null} or empty. - */ - void delete(List entities, String tableName); - /** * @param entities * @param tableName @@ -386,13 +212,6 @@ public interface CassandraOperations extends CqlOperations { */ void delete(List entities, QueryOptions options); - /** - * @param entities - * @param tableName - * @param options - */ - void delete(List entities, String tableName, QueryOptions options); - /** * Remove the given object from the table by id. * @@ -407,21 +226,6 @@ public interface CassandraOperations extends CqlOperations { */ void deleteAsynchronously(T entity, QueryOptions options); - /** - * @param entity - * @param tableName - * @param options - */ - void deleteAsynchronously(T entity, String tableName, QueryOptions options); - - /** - * Removes the given object from the given table. - * - * @param object - * @param table must not be {@literal null} or empty. - */ - void deleteAsynchronously(T entity, String tableName); - /** * Remove the given object from the table by id. * @@ -429,14 +233,6 @@ public interface CassandraOperations extends CqlOperations { */ void deleteAsynchronously(List entities); - /** - * Removes the given object from the given table. - * - * @param object - * @param table must not be {@literal null} or empty. - */ - void deleteAsynchronously(List entities, String tableName); - /** * @param entities * @param tableName @@ -444,17 +240,16 @@ public interface CassandraOperations extends CqlOperations { */ void deleteAsynchronously(List entities, QueryOptions options); - /** - * @param entities - * @param tableName - * @param options - */ - void deleteAsynchronously(List entities, String tableName, QueryOptions options); - /** * Returns the underlying {@link CassandraConverter}. * * @return */ CassandraConverter getConverter(); + + void deleteById(Class type, Object id); + + List selectBySimpleIds(Class type, Iterable ids); + + List selectAll(Class type); } 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..438291ed1 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,67 +16,52 @@ 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.mortbay.log.Log; import org.springframework.cassandra.core.CqlTemplate; import org.springframework.cassandra.core.QueryOptions; import org.springframework.cassandra.core.SessionCallback; +import org.springframework.cassandra.core.util.CollectionUtils; 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 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.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; + protected CassandraConverter cassandraConverter; + protected CassandraMappingContext mappingContext; + protected boolean useFieldAccessOnly = false; /** * Default Constructor for wiring in the required components later @@ -84,15 +69,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,149 +77,15 @@ 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 CassandraMappingContext getCassandraMappingContext() { - return mappingContext; - } + public void setConverter(CassandraConverter cassandraConverter) { - @Override - public Long count(Select selectQuery) { - return doSelectCount(selectQuery); - } + Assert.notNull(cassandraConverter); - @Override - public Long count(String tableName) { - Select select = QueryBuilder.select().countAll().from(tableName); - return doSelectCount(select); - } - - @Override - public void delete(List entities) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - delete(entities, tableName); - } - - @Override - public void delete(List entities, QueryOptions options) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - delete(entities, tableName, options); - } - - @Override - public void delete(List entities, String tableName) { - - delete(entities, tableName, null); - } - - @Override - public void delete(List entities, String tableName, QueryOptions options) { - Assert.notNull(entities); - Assert.notEmpty(entities); - Assert.notNull(tableName); - doBatchDelete(tableName, entities, options, false); - } - - @Override - public void delete(T entity) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - delete(entity, tableName); - } - - @Override - public void delete(T entity, QueryOptions options) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - delete(entity, tableName, options); - } - - @Override - public void delete(T entity, String tableName) { - delete(entity, tableName, null); - } - - @Override - public void delete(T entity, String tableName, QueryOptions options) { - Assert.notNull(entity); - Assert.notNull(tableName); - doDelete(tableName, entity, options, false); - } - - @Override - public void deleteAsynchronously(List entities) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - deleteAsynchronously(entities, tableName); - } - - @Override - public void deleteAsynchronously(List entities, QueryOptions options) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - deleteAsynchronously(entities, tableName, options); - } - - @Override - public void deleteAsynchronously(List entities, String tableName) { - deleteAsynchronously(entities, tableName, null); - } - - @Override - public void deleteAsynchronously(List entities, String tableName, QueryOptions options) { - Assert.notNull(entities); - Assert.notEmpty(entities); - Assert.notNull(tableName); - doBatchDelete(tableName, entities, options, true); - } - - @Override - public void deleteAsynchronously(T entity) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - deleteAsynchronously(entity, tableName); - } - - @Override - public void deleteAsynchronously(T entity, QueryOptions options) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - deleteAsynchronously(entity, tableName, options); - } - - @Override - public void deleteAsynchronously(T entity, String tableName) { - deleteAsynchronously(entity, tableName, null); - } - - @Override - public void deleteAsynchronously(T entity, String tableName, QueryOptions options) { - Assert.notNull(entity); - Assert.notNull(tableName); - doDelete(tableName, entity, options, true); - } - - /** - * @param entityClass - * @return - */ - public String determineTableName(Class entityClass) { - - if (entityClass == null) { - throw new InvalidDataAccessApiUsageException( - "No class parameter provided, entity table name can't be determined!"); - } - - CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - if (entity == null) { - throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " - + entityClass.getName()); - } - return entity.getTableName(); + this.cassandraConverter = cassandraConverter; + mappingContext = cassandraConverter.getCassandraMappingContext(); } @Override @@ -251,253 +93,301 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation return cassandraConverter; } + public CassandraMappingContext getCassandraMappingContext() { + return mappingContext; + } + + public boolean getUseFieldAccessOnly() { + return useFieldAccessOnly; + } + + /** + * Whether only fields should be used when accessing a persistent entity's data. + * + * @param useFieldAccessOnly + */ + public void setUseFieldAccessOnly(boolean useFieldAccessOnly) { + this.useFieldAccessOnly = useFieldAccessOnly; + } + @Override - public String getTableName(Class entityClass) { - return determineTableName(entityClass); + public void afterPropertiesSet() { + super.afterPropertiesSet(); + + Assert.notNull(cassandraConverter); + Assert.notNull(mappingContext); + } + + @Override + public boolean exists(Class type, Object id) { + + Assert.notNull(type); + Assert.notNull(id); + + CassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(type); + + Select select = QueryBuilder.select().countAll().from(entity.getTableName()); + appendIdCriteria(select.where(), entity, id); + + return count(select.getQueryString()) != 0; + } + + @Override + public long count(Class type) { + return count(getTableName(type)); + } + + @Override + public void delete(List entities) { + delete(entities, null); + } + + @Override + public void delete(List entities, QueryOptions options) { + batchDelete(entities, options, false); + } + + @Override + public void deleteById(Class type, Object id) { + + Assert.notNull(type); + Assert.notNull(id); + + CassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(type); + + Delete delete = QueryBuilder.delete().all().from(entity.getTableName()); + appendIdCriteria(delete.where(), entity, id); + + execute(delete.getQueryString()); + } + + @Override + public void delete(T entity) { + delete(entity, null); + } + + @Override + public void delete(T entity, QueryOptions options) { + delete(entity, options, false); + } + + @Override + public void deleteAsynchronously(List entities) { + deleteAsynchronously(entities, null); + } + + @Override + public void deleteAsynchronously(List entities, QueryOptions options) { + batchDelete(entities, options, true); + } + + @Override + public void deleteAsynchronously(T entity) { + deleteAsynchronously(entity, null); + } + + @Override + public void deleteAsynchronously(T entity, QueryOptions options) { + delete(entity, options, true); + } + + @Override + public String getTableName(Class type) { + return mappingContext.getRequiredPersistentEntity(type).getTableName(); } @Override public List insert(List entities) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return insert(entities, tableName); + return insert(entities, null); } @Override public List insert(List entities, QueryOptions options) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return insert(entities, tableName, options); - } - - @Override - public List insert(List entities, String tableName) { - return insert(entities, tableName, null); - } - - @Override - public List insert(List entities, String tableName, QueryOptions options) { - Assert.notNull(entities); - Assert.notEmpty(entities); - Assert.notNull(tableName); - return doBatchInsert(tableName, entities, options, false); + return batchInsert(entities, options, false); } @Override public T insert(T entity) { - String tableName = determineTableName(entity); - Assert.notNull(tableName); - return insert(entity, tableName); + return insert(entity, null); } @Override public T insert(T entity, QueryOptions options) { - String tableName = determineTableName(entity); - Assert.notNull(tableName); - return insert(entity, tableName, options); - } - - @Override - public T insert(T entity, String tableName) { - return insert(entity, tableName, null); - } - - @Override - public T insert(T entity, String tableName, QueryOptions options) { - Assert.notNull(entity); - Assert.notNull(tableName); - ensureNotIterable(entity); - return doInsert(tableName, entity, options, false); + return insert(entity, options, false); } @Override public List insertAsynchronously(List entities) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return insertAsynchronously(entities, tableName); + return insertAsynchronously(entities, null); } @Override public List insertAsynchronously(List entities, QueryOptions options) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return insertAsynchronously(entities, tableName, options); - } - - @Override - public List insertAsynchronously(List entities, String tableName) { - return insertAsynchronously(entities, tableName, null); - } - - @Override - public List insertAsynchronously(List entities, String tableName, QueryOptions options) { - Assert.notNull(entities); - Assert.notEmpty(entities); - Assert.notNull(tableName); - return doBatchInsert(tableName, entities, options, true); + return batchInsert(entities, options, true); } @Override public T insertAsynchronously(T entity) { - String tableName = determineTableName(entity); - Assert.notNull(tableName); - return insertAsynchronously(entity, tableName); + return insertAsynchronously(entity, null); } @Override public T insertAsynchronously(T entity, QueryOptions options) { - String tableName = determineTableName(entity); - Assert.notNull(tableName); - return insertAsynchronously(entity, tableName, options); + return insert(entity, options, true); } @Override - public T insertAsynchronously(T entity, String tableName) { - return insertAsynchronously(entity, tableName, null); + public List selectAll(Class type) { + return select(QueryBuilder.select().all().from(getTableName(type)).getQueryString(), type); } @Override - public T insertAsynchronously(T entity, String tableName, QueryOptions options) { - Assert.notNull(entity); - Assert.notNull(tableName); + public List select(String cql, Class type) { - ensureNotIterable(entity); + Assert.hasText(cql); + Assert.notNull(type); - return doInsert(tableName, entity, options, true); + return select(cql, new CassandraConverterRowCallback(cassandraConverter, type)); } @Override - public List select(Select cql, Class selectClass) { - return select(cql.getQueryString(), selectClass); + public List selectBySimpleIds(Class type, Iterable ids) { + + CassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(type); + + 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", type.getName(), + entity.getIdProperty().getCompositePrimaryKeyEntity().getType().getName())); + } + + Select select = QueryBuilder.select().all().from(entity.getTableName()); + select.where(QueryBuilder.in(entity.getIdProperty().getColumnName(), CollectionUtils.toArray(ids))); + + return select(select.getQueryString(), type); } @Override - public List select(String cql, Class selectClass) { - return doSelect(cql, new ReadRowCallback(cassandraConverter, selectClass)); + public T selectOneById(Class type, Object id) { + + Assert.notNull(type); + Assert.notNull(id); + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(type); + if (entity == null) { + throw new IllegalArgumentException(String.format("unknown entity class [%s]", type.getName())); + } + + Select select = QueryBuilder.select().all().from(entity.getTableName()); + appendIdCriteria(select.where(), entity, id); + + return selectOne(select.getQueryString(), type); + } + + protected interface ClauseCallback { + void doWithClause(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.doWithClause(QueryBuilder.eq(p.getColumnName(), + idWrapper.getProperty(p, p.getActualType(), useFieldAccessOnly))); + } + }); + + return; + } + + clauseCallback.doWithClause(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 doWithClause(Clause clause) { + where.and(clause); + } + }, entity, id); + } + + protected void appendIdCriteria(final Where where, CassandraPersistentEntity entity, Object id) { + + appendIdCriteria(new ClauseCallback() { + + @Override + public void doWithClause(Clause clause) { + where.and(clause); + } + }, entity, id); } @Override - public T selectOne(Select selectQuery, Class selectClass) { - return selectOne(selectQuery.getQueryString(), selectClass); - } - - @Override - public T selectOne(String cql, Class selectClass) { - return doSelectOne(cql, new ReadRowCallback(cassandraConverter, selectClass)); + public T selectOne(String cql, Class type) { + return selectOne(cql, new CassandraConverterRowCallback(cassandraConverter, type)); } @Override public List update(List entities) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return update(entities, tableName); + return update(entities, null); } @Override public List update(List entities, QueryOptions options) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return update(entities, tableName, options); - } - - @Override - public List update(List entities, String tableName) { - return update(entities, tableName, null); - } - - @Override - public List update(List entities, String tableName, QueryOptions options) { - Assert.notNull(entities); - Assert.notEmpty(entities); - Assert.notNull(tableName); - return doBatchUpdate(tableName, entities, options, false); + return batchUpdate(entities, options, false); } @Override public T update(T entity) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - return update(entity, tableName); + return update(entity, null); } @Override public T update(T entity, QueryOptions options) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - return update(entity, tableName, options); - } - - @Override - public T update(T entity, String tableName) { - return update(entity, tableName, null); - } - - @Override - public T update(T entity, String tableName, QueryOptions options) { - Assert.notNull(entity); - Assert.notNull(tableName); - return doUpdate(tableName, entity, options, false); + return update(entity, options, false); } @Override public List updateAsynchronously(List entities) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return updateAsynchronously(entities, tableName); + return updateAsynchronously(entities, null); } @Override public List updateAsynchronously(List entities, QueryOptions options) { - String tableName = getTableName(entities.get(0).getClass()); - Assert.notNull(tableName); - return updateAsynchronously(entities, tableName, options); - } - - @Override - public List updateAsynchronously(List entities, String tableName) { - return updateAsynchronously(entities, tableName, null); - } - - @Override - public List updateAsynchronously(List entities, String tableName, QueryOptions options) { - Assert.notNull(entities); - Assert.notEmpty(entities); - Assert.notNull(tableName); - return doBatchUpdate(tableName, entities, options, true); + return batchUpdate(entities, options, true); } @Override public T updateAsynchronously(T entity) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - return updateAsynchronously(entity, tableName); + return updateAsynchronously(entity, null); } @Override public T updateAsynchronously(T entity, QueryOptions options) { - String tableName = getTableName(entity.getClass()); - Assert.notNull(tableName); - return updateAsynchronously(entity, tableName, options); - } - - @Override - public T updateAsynchronously(T entity, String tableName) { - - return updateAsynchronously(entity, tableName, null); - } - - @Override - public T updateAsynchronously(T entity, String tableName, QueryOptions options) { - Assert.notNull(entity); - Assert.notNull(tableName); - return doUpdate(tableName, entity, options, true); + return update(entity, options, true); } /** * @param obj * @return */ - private String determineTableName(T obj) { + protected String determineTableName(T obj) { if (null != obj) { return determineTableName(obj.getClass()); } @@ -505,12 +395,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation return null; } - /** - * @param query - * @param readRowCallback - * @return - */ - private List doSelect(final String query, ReadRowCallback readRowCallback) { + protected List select(final String query, CassandraConverterRowCallback readRowCallback) { ResultSet resultSet = doExecute(new SessionCallback() { @@ -534,59 +419,16 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation return result; } - /** - * @param selectQuery - * @return - */ - private Long doSelectCount(final Select query) { - - Long count = null; - - ResultSet resultSet = doExecute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session s) throws DataAccessException { - return s.execute(query); - } - }); - - if (resultSet == null) { - return null; - } - - Iterator iterator = resultSet.iterator(); - while (iterator.hasNext()) { - Row row = iterator.next(); - count = row.getLong(0); - } - - return count; - - } - /** * @param query * @param readRowCallback * @return */ - private T doSelectOne(final String query, ReadRowCallback readRowCallback) { + protected T selectOne(String query, CassandraConverterRowCallback readRowCallback) { logger.info(query); - /* - * Run the Query - */ - ResultSet resultSet = doExecute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session s) throws DataAccessException { - return s.execute(query); - } - }); - - if (resultSet == null) { - return null; - } + ResultSet resultSet = query(query); Iterator iterator = resultSet.iterator(); if (iterator.hasNext()) { @@ -607,77 +449,56 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param tableName * @param objectToRemove */ - protected void doBatchDelete(final String tableName, final List entities, final QueryOptions options, - final boolean deleteAsynchronously) { + protected void batchDelete(List entities, QueryOptions options, boolean asynchronously) { Assert.notEmpty(entities); - try { + Batch b = createDeleteBatchQuery(getTableName(entities.get(0).getClass()), entities, options, cassandraConverter); - final Batch b = CqlUtils.toDeleteBatchQuery(tableName, entities, options, cassandraConverter); - logger.info(b.toString()); + logger.info(b.toString()); - doExecute(new SessionCallback() { + String query = b.getQueryString(); - @Override - public Object doInSession(Session s) throws DataAccessException { - - if (deleteAsynchronously) { - s.executeAsync(b); - } else { - s.execute(b); - } - - return null; - - } - }); - - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); + if (asynchronously) { + executeAsynchronously(query); + } else { + execute(query); } } - /** - * Insert a row into a Cassandra CQL Table - * - * @param tableName - * @param entities - * @param optionsByName - * @param insertAsychronously - * @return - */ - protected List doBatchInsert(final String tableName, final List entities, final QueryOptions options, - final boolean insertAsychronously) { + protected T insert(T entity, QueryOptions options, boolean asynchronously) { + + Assert.notNull(entity); + + Insert insert = createInsertQuery(getTableName(entity.getClass()), entity, options, cassandraConverter); + + String query = insert.getQueryString(); + + if (asynchronously) { + executeAsynchronously(query); + } else { + execute(query); + } + + return entity; + } + + protected List batchInsert(List entities, QueryOptions options, boolean asychronously) { Assert.notEmpty(entities); - try { + Batch b = createInsertBatchQuery(getTableName(entities.get(0).getClass()), entities, options, cassandraConverter); - final Batch b = CqlUtils.toInsertBatchQuery(tableName, entities, options, cassandraConverter); - logger.info(b.getQueryString()); + String query = b.getQueryString(); + logger.info(query); - return doExecute(new SessionCallback>() { - - @Override - public List doInSession(Session s) throws DataAccessException { - - if (insertAsychronously) { - s.executeAsync(b); - } else { - s.execute(b); - } - - return entities; - - } - }); - - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); + if (asychronously) { + executeAsynchronously(query); + } else { + execute(query); } + + return entities; } /** @@ -689,136 +510,44 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param updateAsychronously * @return */ - protected List doBatchUpdate(final String tableName, final List entities, final QueryOptions options, - final boolean updateAsychronously) { + protected List batchUpdate(List entities, QueryOptions options, boolean asychronously) { Assert.notEmpty(entities); - try { + Batch b = toUpdateBatchQuery(getTableName(entities.get(0).getClass()), entities, options, cassandraConverter); - final Batch b = CqlUtils.toUpdateBatchQuery(tableName, entities, options, cassandraConverter); - logger.info(b.toString()); + String query = b.getQueryString(); + logger.info(query); - return doExecute(new SessionCallback>() { - - @Override - public List doInSession(Session s) throws DataAccessException { - - if (updateAsychronously) { - s.executeAsync(b); - } else { - s.execute(b); - } - - return entities; - - } - }); - - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); + if (asychronously) { + executeAsynchronously(query); + } else { + execute(query); } + + return entities; } /** * Perform the removal of a Row. * * @param tableName - * @param objectToRemove - */ - protected void doDelete(final String tableName, final T objectToRemove, final QueryOptions options, - final boolean deleteAsynchronously) { - - try { - - final Query q = CqlUtils.toDeleteQuery(tableName, objectToRemove, options, cassandraConverter); - logger.info(q.toString()); - - doExecute(new SessionCallback() { - - @Override - public Object doInSession(Session s) throws DataAccessException { - - if (deleteAsynchronously) { - s.executeAsync(q); - } else { - s.execute(q); - } - - return null; - - } - }); - - } 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); - } - } - - /** - * Insert a row into a Cassandra CQL Table - * - * @param tableName * @param entity */ - protected T doInsert(final String tableName, final T entity, final QueryOptions options, - final boolean insertAsychronously) { + protected void delete(T entity, QueryOptions options, boolean asynchronously) { - try { + Assert.notNull(entity); - final Query q = CqlUtils.toInsertQuery(tableName, entity, options, cassandraConverter); + Delete delete = createDeleteQuery(getTableName(entity.getClass()), entity, options, cassandraConverter); + logger.info(delete.toString()); - logger.info(q.toString()); - if (q.getConsistencyLevel() != null) { - logger.info(q.getConsistencyLevel().name()); - } - if (q.getRetryPolicy() != null) { - logger.info(q.getRetryPolicy().toString()); - } + String query = delete.getQueryString(); - 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)); + if (asynchronously) { + executeAsynchronously(query); + } else { + execute(query); } - } /** @@ -830,47 +559,199 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation * @param updateAsychronously * @return */ - protected T doUpdate(final String tableName, final T entity, final QueryOptions options, - final boolean updateAsychronously) { + protected T update(T entity, QueryOptions options, boolean asychronously) { - try { + Assert.notNull(entity); - final Query q = CqlUtils.toUpdateQuery(tableName, entity, options, cassandraConverter); - logger.info(q.toString()); + Update q = toUpdateQuery(getTableName(entity.getClass()), entity, options, cassandraConverter); - return doExecute(new SessionCallback() { + String query = q.getQueryString(); + logger.info(query); - @Override - public T doInSession(Session s) throws DataAccessException { - - if (updateAsychronously) { - s.executeAsync(q); - } else { - s.execute(q); - } - - return entity; - - } - }); - - } catch (EntityWriterException e) { - throw getExceptionTranslator().translateExceptionIfPossible( - new RuntimeException("Failed to translate Object to Query", e)); + if (asychronously) { + executeAsynchronously(query); + } else { + execute(query); } + return entity; + } + + /** + * 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 Insert createInsertQuery(String tableName, Object objectToSave, QueryOptions options, + EntityWriter entityWriter) { + + 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 Update toUpdateQuery(String tableName, Object objectToSave, QueryOptions options, + EntityWriter entityWriter) { + + 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; + } /** - * Verify the object is not an iterable type + * Generates a Batch Object for multiple Updates * - * @param o + * @param tableName + * @param objectsToSave + * @param entity + * @param optionsByName + * + * @return The Query object to run with session.execute(); */ - 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."); - } + public static Batch toUpdateBatchQuery(String tableName, List objectsToSave, QueryOptions options, + EntityWriter entityWriter) { + + /* + * Return variable is a Batch statement + */ + Batch b = QueryBuilder.batch(); + + for (T objectToSave : objectsToSave) { + + b.add(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(String tableName, List entities, QueryOptions options, + EntityWriter entityWriter) { + + Batch batch = QueryBuilder.batch(); + + for (T entity : entities) { + batch.add(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 Delete createDeleteQuery(String tableName, 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) { + + Assert.notEmpty(entities); + Assert.hasText(tableName); + + Batch batch = QueryBuilder.batch(); + + for (T entity : entities) { + batch.add(createDeleteQuery(tableName, entity, options, entityWriter)); + } + + CqlTemplate.addQueryOptions(batch, options); + + return batch; } } 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/CassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java index b0e996964..126605c9a 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java @@ -2,6 +2,7 @@ package org.springframework.data.cassandra.mapping; import org.springframework.cassandra.core.keyspace.CreateTableSpecification; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.util.TypeInformation; import com.datastax.driver.core.TableMetadata; @@ -26,4 +27,24 @@ public interface CassandraMappingContext extends * @param table May not be null. */ boolean usesTable(TableMetadata table); + + /** + * Returns the {@link CassandraPersistentEntity} for the given type. If it doesn't exist, this method throws + * {@link IllegalArgumentException}. + * + * @param type The Java type of the persistent entity. + * @return The {@link CassandraPersistentEntity} describing the persistent Java type. + * @throws IllegalArgumentException if the persistent entity is unknown + */ + public CassandraPersistentEntity getRequiredPersistentEntity(Class type); + + /** + * Returns the {@link CassandraPersistentEntity} for the given type. If it doesn't exist, this method throws + * {@link IllegalArgumentException}. + * + * @param type The {@link TypeInformation} of the persistent entity. + * @return The {@link CassandraPersistentEntity} describing the persistent Java type. + * @throws IllegalArgumentException if the persistent entity is unknown + */ + public CassandraPersistentEntity getRequiredPersistentEntity(TypeInformation type); } 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()); } } @@ -148,4 +148,29 @@ public class DefaultCassandraMappingContext extends return spec; } + + @Override + public CassandraPersistentEntity getRequiredPersistentEntity(Class type) { + + CassandraPersistentEntity entity = getPersistentEntity(type); + + if (entity == null) { + throw new IllegalArgumentException(String.format("no persistence metadata found for type [%s]", type.getName())); + } + + return entity; + } + + @Override + public CassandraPersistentEntity getRequiredPersistentEntity(TypeInformation type) { + + CassandraPersistentEntity entity = getPersistentEntity(type); + + if (entity == null) { + throw new IllegalArgumentException(String.format("no persistence metadata found for type [%s]", + type.getActualType())); + } + + return entity; + } } 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/PrimaryKeyClass.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKeyClass.java index c99049bd5..613809e86 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKeyClass.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKeyClass.java @@ -21,6 +21,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.data.annotation.Persistent; + /** * Annotates a type that represents the identity type of another class whose instances are stored in a table. *

@@ -33,5 +35,6 @@ import java.lang.annotation.Target; @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) +@Persistent public @interface PrimaryKeyClass { } 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..2d0727ea8 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 @@ -22,6 +22,7 @@ import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; import org.springframework.data.cassandra.repository.query.CassandraEntityInformation; import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.repository.core.support.AbstractEntityInformation; +import org.springframework.util.Assert; /** * {@link CassandraEntityInformation} implementation using a {@link CassandraPersistentEntity} instance to lookup the @@ -59,49 +60,28 @@ 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) { - CassandraPersistentProperty idProperty = entityMetadata.getIdProperty(); + Assert.notNull(entity); + CassandraPersistentProperty idProperty = entityMetadata.getIdProperty(); if (idProperty == null) { return null; } - try { - return (ID) BeanWrapper.create(entity, null).getProperty(idProperty); - } catch (Exception e) { - throw new RuntimeException(e); - } + return (ID) BeanWrapper.create(entity, null).getProperty(idProperty); } - /* (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..0f104af68 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 @@ -16,33 +16,27 @@ package org.springframework.data.cassandra.repository.support; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import org.springframework.cassandra.core.CqlOperations; +import org.springframework.cassandra.core.util.CollectionUtils; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.data.cassandra.repository.CassandraRepository; import org.springframework.data.cassandra.repository.query.CassandraEntityInformation; import org.springframework.util.Assert; -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; /** * Repository base implementation for Cassandra. * * @author Alex Shvid - * + * @author Matthew T. Adams */ - public class SimpleCassandraRepository implements CassandraRepository { - private final CassandraTemplate cassandraTemplate; - private final CassandraEntityInformation entityInformation; + protected CassandraOperations template; + protected CassandraEntityInformation entityInformation; /** * Creates a new {@link SimpleCassandraRepository} for the given {@link CassandraEntityInformation} and @@ -51,201 +45,71 @@ 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 template) { - Assert.notNull(cassandraTemplate); + Assert.notNull(template); Assert.notNull(metadata); this.entityInformation = metadata; - this.cassandraTemplate = cassandraTemplate; + this.template = template; } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object) - */ - public S save(S entity) { - - Assert.notNull(entity, "Entity must not be null!"); - cassandraTemplate.insert(entity, entityInformation.getTableName()); - return entity; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable) - */ - public List save(Iterable entities) { - - Assert.notNull(entities, "The given Iterable of entities not be null!"); - - List result = new ArrayList(); - - for (S entity : entities) { - save(entity); - result.add(entity); - } - - 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 S save(S entity) { + return template.insert(entity); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable) - */ + @Override + public List save(Iterable entities) { + return template.insert(CollectionUtils.toList(entities)); + } + + @Override + public T findOne(ID id) { + return template.selectOneById(entityInformation.getJavaType(), id); + } + + @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 template.exists(entityInformation.getJavaType(), id); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#count() - */ + @Override public long count() { - return cassandraTemplate.count(entityInformation.getTableName()); + return template.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()); + template.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)); } - /* - * (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!"); - - for (T entity : entities) { - delete(entity); - } + template.delete(CollectionUtils.toList(entities)); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#deleteAll() - */ + @Override public void deleteAll() { - cassandraTemplate.truncate(entityInformation.getTableName()); + template.truncate(entityInformation.getTableName()); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.CrudRepository#findAll() - */ + @Override public List findAll() { - Select select = QueryBuilder.select().all().from(entityInformation.getTableName()); - return findAll(select); + return template.selectAll(entityInformation.getJavaType()); } - /* - * (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 template.selectBySimpleIds(entityInformation.getJavaType(), ids); } - private List findAll(Select query) { - - if (query == null) { - return Collections.emptyList(); - } - - return cassandraTemplate.select(query, entityInformation.getJavaType()); + protected List findAll(Select query) { + return template.select(query.getQueryString(), entityInformation.getJavaType()); } - - /** - * Returns the underlying {@link CqlOperations} instance. - * - * @return - */ - protected CqlOperations getCassandraOperations() { - return this.cassandraTemplate; - } - - /** - * Returns the underlying {@link CassandraOperations} instance. - * - * @return - */ - protected CassandraOperations getCassandraDataOperations() { - return this.cassandraTemplate; - } - - /** - * @return the entityInformation - */ - protected CassandraEntityInformation getEntityInformation() { - return entityInformation; - } - } 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/AbstractSpringDataEmbeddedCassandraIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/AbstractSpringDataEmbeddedCassandraIntegrationTest.java index da701f3ff..caf1cb27b 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/AbstractSpringDataEmbeddedCassandraIntegrationTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/AbstractSpringDataEmbeddedCassandraIntegrationTest.java @@ -1,14 +1,84 @@ package org.springframework.data.cassandra.test.integration; +import static org.springframework.cassandra.core.keyspace.DropTableSpecification.dropTable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cassandra.test.integration.AbstractEmbeddedCassandraIntegrationTest; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.test.integration.support.SpringDataBuildProperties; +import org.springframework.data.cassandra.test.integration.template.CassandraDataOperationsTest.Config; +import org.springframework.util.Assert; + +import com.datastax.driver.core.TableMetadata; public class AbstractSpringDataEmbeddedCassandraIntegrationTest extends AbstractEmbeddedCassandraIntegrationTest { - static { - // override necessary superclass statics + public static List SCRIPT; + public static List TABLES; + static { SpringDataBuildProperties props = new SpringDataBuildProperties(); CASSANDRA_NATIVE_PORT = props.getCassandraPort(); } + + public Logger log = LoggerFactory.getLogger(getClass()); + + @Autowired + public CassandraOperations template; + + /** + * Saves all table metadata, then drops & creates all tables. + */ + public void recreateAllTables() { + + saveAllTableMetadata(); + + for (TableMetadata table : TABLES) { + template.execute(dropTable(table.getName())); + template.execute(table.asCQLQuery()); + } + } + + public void saveAllTableMetadata() { + saveAllTableMetadata(false); + } + + /** + * Saves all table metadata statically. + */ + public void saveAllTableMetadata(boolean force) { + + if (TABLES != null && !force) { + return; + } + + TABLES = new ArrayList(template.getSession().getCluster().getMetadata() + .getKeyspace(Config.KEYSPACE_NAME).getTables()); + } + + public List readScriptLines(String resourceName) { + return readScriptLines(resourceName); + } + + /** + * Reads the lines from the script referenced by {@link #RESOURCE}. + */ + public List readScriptLines(String resourceName, boolean force) throws IOException { + + if (SCRIPT != null && !force) { + return SCRIPT; + } + + Assert.hasText(resourceName); + + return SCRIPT = FileUtils.readLines(new ClassPathResource(resourceName).getFile()); + } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Comment.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Comment.java similarity index 96% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Comment.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Comment.java index f01875f11..30e8b06ca 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Comment.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Comment.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; import java.util.Set; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/CommentPK.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentPK.java similarity index 95% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/CommentPK.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentPK.java index 3a870b9ea..8aa49748e 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/CommentPK.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentPK.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Notification.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Notification.java similarity index 96% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Notification.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Notification.java index 999623fa9..1a4cd4a44 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Notification.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Notification.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/NotificationPK.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/NotificationPK.java similarity index 96% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/NotificationPK.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/NotificationPK.java index 1689860e6..be92429b7 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/NotificationPK.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/NotificationPK.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Post.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Post.java similarity index 97% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Post.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Post.java index 89dcc4cc3..3c841695a 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Post.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Post.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; import java.util.Map; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/PostPK.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/PostPK.java similarity index 96% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/PostPK.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/PostPK.java index fcdd78455..aa913449c 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/PostPK.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/PostPK.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Timeline.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Timeline.java similarity index 95% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Timeline.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Timeline.java index 78d3d6854..18666ea47 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Timeline.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/Timeline.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/TimelinePK.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/TimelinePK.java similarity index 95% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/TimelinePK.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/TimelinePK.java index 1107868f1..ae6e296bb 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/TimelinePK.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/TimelinePK.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.composites; import java.util.Date; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests.java index 3099814bc..66eb3ee1a 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests.java @@ -1,38 +1,21 @@ package org.springframework.data.cassandra.test.integration.config; -import java.io.IOException; - -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.thrift.transport.TTransportException; -import org.cassandraunit.utils.EmbeddedCassandraServerHelper; -import org.junit.After; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.data.cassandra.test.integration.AbstractSpringDataEmbeddedCassandraIntegrationTest; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.Assert; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class CassandraNamespaceTests { +public class CassandraNamespaceTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest { @Autowired private ApplicationContext ctx; - @BeforeClass - public static void startCassandra() throws IOException, TTransportException, ConfigurationException, - InterruptedException { - EmbeddedCassandraServerHelper.startEmbeddedCassandra("spring-cassandra.yaml"); - } - - @After - public void clearCassandra() { - EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); - } - @Test public void test() { Assert.notNull(ctx); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/TestConfig.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/TestConfig.java index 9ec75b2bc..f83cc04f4 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/TestConfig.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/config/TestConfig.java @@ -1,5 +1,6 @@ package org.springframework.data.cassandra.test.integration.config; +import org.springframework.cassandra.test.unit.support.Utils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.cassandra.config.java.AbstractSpringDataCassandraConfiguration; @@ -23,7 +24,7 @@ public class TestConfig extends AbstractSpringDataCassandraConfiguration { public static final int PORT = PROPS.getCassandraPort(); public static final int RPC_PORT = PROPS.getCassandraRpcPort(); - public static final String KEYSPACE_NAME = "test"; + public static final String KEYSPACE_NAME = Utils.randomKeyspaceName(); @Override protected String getKeyspaceName() { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityIntegrationTests.java index b5f2e88d6..69dda8e6b 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/BasicCassandraPersistentEntityIntegrationTests.java @@ -19,13 +19,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; -import java.io.IOException; - -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.thrift.transport.TTransportException; import org.cassandraunit.utils.EmbeddedCassandraServerHelper; import org.junit.After; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -33,6 +28,7 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.context.ApplicationContext; import org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntity; import org.springframework.data.cassandra.mapping.Table; +import org.springframework.data.cassandra.test.integration.AbstractSpringDataEmbeddedCassandraIntegrationTest; import org.springframework.data.util.ClassTypeInformation; /** @@ -41,17 +37,11 @@ import org.springframework.data.util.ClassTypeInformation; * @author Alex Shvid */ @RunWith(MockitoJUnitRunner.class) -public class BasicCassandraPersistentEntityIntegrationTests { +public class BasicCassandraPersistentEntityIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest { @Mock ApplicationContext context; - @BeforeClass - public static void startCassandra() throws IOException, TTransportException, ConfigurationException, - InterruptedException { - EmbeddedCassandraServerHelper.startEmbeddedCassandra("spring-cassandra.yaml"); - } - @Test public void subclassInheritsAtDocumentAnnotation() { @@ -84,11 +74,6 @@ public class BasicCassandraPersistentEntityIntegrationTests { assertThat(entity.getTableName(), is(bean.tableName)); } - @After - public void clearCassandra() { - EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); - } - @Table("messages") static class Message { 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()); } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java index 8820a6608..2de6a2a07 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java @@ -68,7 +68,7 @@ public class UserRepositoryJavaConfigIntegrationTests extends AbstractSpringData } @Override - public String getMappingBasePackage() { + public String getEntityBasePackage() { return User.class.getPackage().getName(); } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Book.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/Book.java similarity index 96% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Book.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/Book.java index 9ba3dd3b9..95fcfd700 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/Book.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/Book.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.simpletons; import org.springframework.data.cassandra.mapping.PrimaryKey; import org.springframework.data.cassandra.mapping.Table; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/LogEntry.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/LogEntry.java similarity index 95% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/LogEntry.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/LogEntry.java index 3ba920b46..b40d9f379 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/table/LogEntry.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/simpletons/LogEntry.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.table; +package org.springframework.data.cassandra.test.integration.simpletons; import java.util.Date; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/ManualDataSet.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/ManualDataSet.java new file mode 100644 index 000000000..805b9db13 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/ManualDataSet.java @@ -0,0 +1,22 @@ +package org.springframework.data.cassandra.test.integration.support; + +import org.cassandraunit.dataset.commons.AbstractCommonsParserDataSet; +import org.cassandraunit.dataset.commons.ParsedKeyspace; + +public class ManualDataSet extends AbstractCommonsParserDataSet { + + private String keyspaceName; + private ParsedKeyspace pks; + + public ManualDataSet(String keyspaceName) { + this.keyspaceName = keyspaceName; + + pks = new ParsedKeyspace(); + pks.setName(this.keyspaceName); + } + + @Override + protected ParsedKeyspace getParsedKeyspace() { + return pks; + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraAdminTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraAdminTest.java deleted file mode 100644 index d5ccd5d79..000000000 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraAdminTest.java +++ /dev/null @@ -1,102 +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.test.integration.template; - -import java.io.IOException; - -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.thrift.transport.TTransportException; -import org.cassandraunit.DataLoader; -import org.cassandraunit.dataset.yaml.ClassPathYamlDataSet; -import org.cassandraunit.utils.EmbeddedCassandraServerHelper; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cassandra.core.CqlOperations; -import org.springframework.context.ApplicationContext; -import org.springframework.data.cassandra.test.integration.config.TestConfig; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.AnnotationConfigContextLoader; - -/** - * @author David Webb - * - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class) -public class CassandraAdminTest { - - @Autowired - private CqlOperations cassandraTemplate; - - @Mock - ApplicationContext context; - - @BeforeClass - public static void startCassandra() throws IOException, TTransportException, ConfigurationException, - InterruptedException { - EmbeddedCassandraServerHelper.startEmbeddedCassandra("spring-cassandra.yaml"); - - /* - * Load data file to creat the test keyspace before we init the template - */ - DataLoader dataLoader = new DataLoader("Test Cluster", "localhost:" + TestConfig.RPC_PORT); - dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml")); - - } - - @Before - public void setupKeyspace() { - - /* - * Load data file to creat the test keyspace before we init the template - */ - DataLoader dataLoader = new DataLoader("Test Cluster", "localhost:" + TestConfig.RPC_PORT); - dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml")); - - } - - @Test - public void alterTableTest() { - - // cassandraTemplate.alterTable(UserAlter.class); - - } - - @Test - public void dropTableTest() { - - // cassandraTemplate.dropTable(User.class); - // cassandraTemplate.dropTable("comments"); - - } - - @After - public void clearCassandra() { - EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); - } - - @AfterClass - public static void stopCassandra() { - EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); - } -} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java index 56ae82be3..173729928 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java @@ -15,22 +15,18 @@ */ package org.springframework.data.cassandra.test.integration.template; +import static org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification.createKeyspace; +import static org.springframework.cassandra.core.keyspace.DropTableSpecification.dropTable; + import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.thrift.transport.TTransportException; -import org.cassandraunit.CassandraCQLUnit; -import org.cassandraunit.DataLoader; -import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; -import org.cassandraunit.dataset.yaml.ClassPathYamlDataSet; -import org.cassandraunit.utils.EmbeddedCassandraServerHelper; -import org.junit.After; +import org.apache.commons.io.FileUtils; import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Rule; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; @@ -39,14 +35,19 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cassandra.core.ConsistencyLevel; import org.springframework.cassandra.core.QueryOptions; import org.springframework.cassandra.core.RetryPolicy; +import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.data.cassandra.config.SchemaAction; import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.test.integration.AbstractSpringDataEmbeddedCassandraIntegrationTest; import org.springframework.data.cassandra.test.integration.config.TestConfig; -import org.springframework.data.cassandra.test.integration.support.SpringDataBuildProperties; -import org.springframework.data.cassandra.test.integration.table.Book; +import org.springframework.data.cassandra.test.integration.simpletons.Book; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.AnnotationConfigContextLoader; +import com.datastax.driver.core.TableMetadata; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; @@ -57,51 +58,43 @@ import com.datastax.driver.core.querybuilder.Select; * */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class) -public class CassandraDataOperationsTest { +@ContextConfiguration +public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassandraIntegrationTest { - @Autowired - private CassandraOperations cassandraTemplate; + @Configuration + public static class Config extends TestConfig { - private static Logger log = LoggerFactory.getLogger(CassandraDataOperationsTest.class); + @Override + public SchemaAction getSchemaAction() { + return SchemaAction.RECREATE; + } - public static final SpringDataBuildProperties PROPS = new SpringDataBuildProperties(); - private final static String CASSANDRA_CONFIG = "spring-cassandra.yaml"; - private final static String KEYSPACE_NAME = "test"; - private final static String CASSANDRA_HOST = "localhost"; - private final static int CASSANDRA_NATIVE_PORT = PROPS.getCassandraPort(); - private final static int CASSANDRA_THRIFT_PORT = PROPS.getCassandraRpcPort(); + @Override + protected List getKeyspaceCreations() { + return Arrays.asList(createKeyspace().name(getKeyspaceName()).withSimpleReplication()); + } - @Rule - public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet("cql-dataload.cql", - KEYSPACE_NAME), CASSANDRA_CONFIG, CASSANDRA_HOST, CASSANDRA_NATIVE_PORT); + @Override + public String getEntityBasePackage() { + return Book.class.getPackage().getName(); + } + } - @BeforeClass - public static void startCassandra() throws IOException, TTransportException, ConfigurationException, - InterruptedException { - - EmbeddedCassandraServerHelper.startEmbeddedCassandra(CASSANDRA_CONFIG); - - /* - * Load data file to creat the test keyspace before we init the template - */ - DataLoader dataLoader = new DataLoader("Test Cluster", CASSANDRA_HOST + ":" + CASSANDRA_THRIFT_PORT); - dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml")); + @Before + public void before() throws IOException { + recreateAllTables(); } @Test public void insertTest() { - /* - * Test Single Insert with entity - */ Book b1 = new Book(); b1.setIsbn("123456-1"); b1.setTitle("Spring Data Cassandra Guide"); b1.setAuthor("Cassandra Guru"); b1.setPages(521); - cassandraTemplate.insert(b1); + template.insert(b1); Book b2 = new Book(); b2.setIsbn("123456-2"); @@ -109,11 +102,8 @@ public class CassandraDataOperationsTest { b2.setAuthor("Cassandra Guru"); b2.setPages(521); - cassandraTemplate.insert(b2, "book_alt"); + template.insert(b2); - /* - * Test Single Insert with entity - */ Book b3 = new Book(); b3.setIsbn("123456-3"); b3.setTitle("Spring Data Cassandra Guide"); @@ -124,34 +114,27 @@ public class CassandraDataOperationsTest { options.setConsistencyLevel(ConsistencyLevel.ONE); options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); - cassandraTemplate.insert(b3, "book", options); + template.insert(b3, options); - /* - * Test Single Insert with entity - */ Book b5 = new Book(); b5.setIsbn("123456-5"); b5.setTitle("Spring Data Cassandra Guide"); b5.setAuthor("Cassandra Guru"); b5.setPages(265); - cassandraTemplate.insert(b5, options); - + template.insert(b5, options); } @Test public void insertAsynchronouslyTest() { - /* - * Test Single Insert with entity - */ Book b1 = new Book(); b1.setIsbn("123456-1"); b1.setTitle("Spring Data Cassandra Guide"); b1.setAuthor("Cassandra Guru"); b1.setPages(521); - cassandraTemplate.insertAsynchronously(b1); + template.insertAsynchronously(b1); Book b2 = new Book(); b2.setIsbn("123456-2"); @@ -159,7 +142,7 @@ public class CassandraDataOperationsTest { b2.setAuthor("Cassandra Guru"); b2.setPages(521); - cassandraTemplate.insertAsynchronously(b2, "book_alt"); + template.insertAsynchronously(b2); /* * Test Single Insert with entity @@ -174,7 +157,7 @@ public class CassandraDataOperationsTest { options.setConsistencyLevel(ConsistencyLevel.ONE); options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); - cassandraTemplate.insertAsynchronously(b3, "book", options); + template.insertAsynchronously(b3, options); /* * Test Single Insert with entity @@ -194,7 +177,7 @@ public class CassandraDataOperationsTest { b5.setAuthor("Cassandra Guru"); b5.setPages(265); - cassandraTemplate.insertAsynchronously(b5, options); + template.insertAsynchronously(b5, options); } @@ -209,19 +192,19 @@ public class CassandraDataOperationsTest { books = getBookList(20); - cassandraTemplate.insert(books); + template.insert(books); books = getBookList(20); - cassandraTemplate.insert(books, "book_alt"); + template.insert(books); books = getBookList(20); - cassandraTemplate.insert(books, "book", options); + template.insert(books, options); books = getBookList(20); - cassandraTemplate.insert(books, options); + template.insert(books, options); } @@ -236,19 +219,19 @@ public class CassandraDataOperationsTest { books = getBookList(20); - cassandraTemplate.insertAsynchronously(books); + template.insertAsynchronously(books); books = getBookList(20); - cassandraTemplate.insertAsynchronously(books, "book_alt"); + template.insertAsynchronously(books); books = getBookList(20); - cassandraTemplate.insertAsynchronously(books, "book", options); + template.insertAsynchronously(books, options); books = getBookList(20); - cassandraTemplate.insertAsynchronously(books, options); + template.insertAsynchronously(books, options); } @@ -290,7 +273,7 @@ public class CassandraDataOperationsTest { b1.setAuthor("Cassandra Guru"); b1.setPages(521); - cassandraTemplate.update(b1); + template.update(b1); Book b2 = new Book(); b2.setIsbn("123456-2"); @@ -298,7 +281,7 @@ public class CassandraDataOperationsTest { b2.setAuthor("Cassandra Guru"); b2.setPages(521); - cassandraTemplate.update(b2, "book_alt"); + template.update(b2); /* * Test Single Insert with entity @@ -309,7 +292,7 @@ public class CassandraDataOperationsTest { b3.setAuthor("Cassandra Guru"); b3.setPages(265); - cassandraTemplate.update(b3, "book", options); + template.update(b3, options); /* * Test Single Insert with entity @@ -320,7 +303,7 @@ public class CassandraDataOperationsTest { b5.setAuthor("Cassandra Guru"); b5.setPages(265); - cassandraTemplate.update(b5, options); + template.update(b5, options); } @@ -342,7 +325,7 @@ public class CassandraDataOperationsTest { b1.setAuthor("Cassandra Guru"); b1.setPages(521); - cassandraTemplate.updateAsynchronously(b1); + template.updateAsynchronously(b1); Book b2 = new Book(); b2.setIsbn("123456-2"); @@ -350,7 +333,7 @@ public class CassandraDataOperationsTest { b2.setAuthor("Cassandra Guru"); b2.setPages(521); - cassandraTemplate.updateAsynchronously(b2, "book_alt"); + template.updateAsynchronously(b2); /* * Test Single Insert with entity @@ -361,7 +344,7 @@ public class CassandraDataOperationsTest { b3.setAuthor("Cassandra Guru"); b3.setPages(265); - cassandraTemplate.updateAsynchronously(b3, "book", options); + template.updateAsynchronously(b3, options); /* * Test Single Insert with entity @@ -372,7 +355,7 @@ public class CassandraDataOperationsTest { b5.setAuthor("Cassandra Guru"); b5.setPages(265); - cassandraTemplate.updateAsynchronously(b5, options); + template.updateAsynchronously(b5, options); } @@ -387,35 +370,35 @@ public class CassandraDataOperationsTest { books = getBookList(20); - cassandraTemplate.insert(books); + template.insert(books); alterBooks(books); - cassandraTemplate.update(books); + template.update(books); books = getBookList(20); - cassandraTemplate.insert(books, "book_alt"); + template.insert(books); alterBooks(books); - cassandraTemplate.update(books, "book_alt"); + template.update(books); books = getBookList(20); - cassandraTemplate.insert(books, "book", options); + template.insert(books, options); alterBooks(books); - cassandraTemplate.update(books, "book", options); + template.update(books, options); books = getBookList(20); - cassandraTemplate.insert(books, options); + template.insert(books, options); alterBooks(books); - cassandraTemplate.update(books, options); + template.update(books, options); } @@ -430,35 +413,35 @@ public class CassandraDataOperationsTest { books = getBookList(20); - cassandraTemplate.insert(books); + template.insert(books); alterBooks(books); - cassandraTemplate.updateAsynchronously(books); + template.updateAsynchronously(books); books = getBookList(20); - cassandraTemplate.insert(books, "book_alt"); + template.insert(books); alterBooks(books); - cassandraTemplate.updateAsynchronously(books, "book_alt"); + template.updateAsynchronously(books); books = getBookList(20); - cassandraTemplate.insert(books, "book", options); + template.insert(books, options); alterBooks(books); - cassandraTemplate.updateAsynchronously(books, "book", options); + template.updateAsynchronously(books, options); books = getBookList(20); - cassandraTemplate.insert(books, options); + template.insert(books, options); alterBooks(books); - cassandraTemplate.updateAsynchronously(books, options); + template.updateAsynchronously(books, options); } @@ -489,12 +472,12 @@ public class CassandraDataOperationsTest { Book b1 = new Book(); b1.setIsbn("123456-1"); - cassandraTemplate.delete(b1); + template.delete(b1); Book b2 = new Book(); b2.setIsbn("123456-2"); - cassandraTemplate.delete(b2, "book_alt"); + template.delete(b2); /* * Test Single Insert with entity @@ -502,7 +485,7 @@ public class CassandraDataOperationsTest { Book b3 = new Book(); b3.setIsbn("123456-3"); - cassandraTemplate.delete(b3, "book", options); + template.delete(b3, options); /* * Test Single Insert with entity @@ -510,7 +493,7 @@ public class CassandraDataOperationsTest { Book b5 = new Book(); b5.setIsbn("123456-5"); - cassandraTemplate.delete(b5, options); + template.delete(b5, options); } @@ -529,12 +512,12 @@ public class CassandraDataOperationsTest { Book b1 = new Book(); b1.setIsbn("123456-1"); - cassandraTemplate.deleteAsynchronously(b1); + template.deleteAsynchronously(b1); Book b2 = new Book(); b2.setIsbn("123456-2"); - cassandraTemplate.deleteAsynchronously(b2, "book_alt"); + template.deleteAsynchronously(b2); /* * Test Single Insert with entity @@ -542,7 +525,7 @@ public class CassandraDataOperationsTest { Book b3 = new Book(); b3.setIsbn("123456-3"); - cassandraTemplate.deleteAsynchronously(b3, "book", options); + template.deleteAsynchronously(b3, options); /* * Test Single Insert with entity @@ -550,7 +533,7 @@ public class CassandraDataOperationsTest { Book b5 = new Book(); b5.setIsbn("123456-5"); - cassandraTemplate.deleteAsynchronously(b5, options); + template.deleteAsynchronously(b5, options); } @@ -565,27 +548,27 @@ public class CassandraDataOperationsTest { books = getBookList(20); - cassandraTemplate.insert(books); + template.insert(books); - cassandraTemplate.delete(books); + template.delete(books); books = getBookList(20); - cassandraTemplate.insert(books, "book_alt"); + template.insert(books); - cassandraTemplate.delete(books, "book_alt"); + template.delete(books); books = getBookList(20); - cassandraTemplate.insert(books, "book", options); + template.insert(books, options); - cassandraTemplate.delete(books, "book", options); + template.delete(books, options); books = getBookList(20); - cassandraTemplate.insert(books, options); + template.insert(books, options); - cassandraTemplate.delete(books, options); + template.delete(books, options); } @@ -600,27 +583,27 @@ public class CassandraDataOperationsTest { books = getBookList(20); - cassandraTemplate.insert(books); + template.insert(books); - cassandraTemplate.deleteAsynchronously(books); + template.deleteAsynchronously(books); books = getBookList(20); - cassandraTemplate.insert(books, "book_alt"); + template.insert(books); - cassandraTemplate.deleteAsynchronously(books, "book_alt"); + template.deleteAsynchronously(books); books = getBookList(20); - cassandraTemplate.insert(books, "book", options); + template.insert(books, options); - cassandraTemplate.deleteAsynchronously(books, "book", options); + template.deleteAsynchronously(books, options); books = getBookList(20); - cassandraTemplate.insert(books, options); + template.insert(books, options); - cassandraTemplate.deleteAsynchronously(books, options); + template.deleteAsynchronously(books, options); } @@ -636,12 +619,12 @@ public class CassandraDataOperationsTest { b1.setAuthor("Cassandra Guru"); b1.setPages(521); - cassandraTemplate.insert(b1); + template.insert(b1); Select select = QueryBuilder.select().all().from("book"); select.where(QueryBuilder.eq("isbn", "123456-1")); - Book b = cassandraTemplate.selectOne(select, Book.class); + Book b = template.selectOne(select.getQueryString(), Book.class); log.info("SingleSelect Book Title -> " + b.getTitle()); log.info("SingleSelect Book Author -> " + b.getAuthor()); @@ -656,11 +639,11 @@ public class CassandraDataOperationsTest { List books = getBookList(20); - cassandraTemplate.insert(books); + template.insert(books); Select select = QueryBuilder.select().all().from("book"); - List b = cassandraTemplate.select(select, Book.class); + List b = template.select(select.getQueryString(), Book.class); log.info("Book Count -> " + b.size()); @@ -671,23 +654,11 @@ public class CassandraDataOperationsTest { @Test public void selectCountTest() { - List books = getBookList(20); + int count = 20; + List books = getBookList(count); - cassandraTemplate.insert(books); - - Select select = QueryBuilder.select().countAll().from("book"); - - Long count = cassandraTemplate.count(select); - - log.info("Book Count -> " + count); - - Assert.assertEquals(count, new Long(20)); - - } - - @After - public void clearCassandra() { - EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); + template.insert(books); + Assert.assertEquals(count, template.count(Book.class)); } } diff --git a/spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml b/spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml index a0e13da6e..3442321c5 100644 --- a/spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml +++ b/spring-data-cassandra/src/test/resources/cassandra-keyspace.yaml @@ -1,3 +1,3 @@ -name: test +name: ks9487fb2da1a541e39a05e5545d64411a replicationFactor: 1 strategy: org.apache.cassandra.locator.SimpleStrategy \ No newline at end of file