DATACASS-292 - Provide revised synchronous and asynchronous CQL and Cassandra templates.
We now provide revised CQL and Cassandra templates as central classes to interact with CQL and Cassandra with object mapping. Previously, synchronous and asynchronous methods were exposed inside the same interfaces that made it hard to chose the right method. The revised Template API consists of: * CqlTemplate * AsyncCqlTemplate * CassandraTemplate * AsyncCassandraTemplate CassandraTemplate and AsyncCassandraTemplate reuse CqlTemplate and AsyncCqlTemplate instead of extending from these. This is, to not mix methods using conversion/object mapping with lower level CQL execution methods. AsyncCqlTemplate and AsyncCassandraTemplate are all new and benefit from ListenableFuture as synchronization aid. They no longer rely on various callback-interfaces. CassandraTemplate and AsyncCassandraTemplate no longer provide insert/update/delete methods accepting a collection of items. Use CassandraBatchOperations for atomic batches to group operations.
This commit is contained in:
@@ -0,0 +1,722 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
|
||||
import com.datastax.driver.core.PreparedStatement;
|
||||
import com.datastax.driver.core.ResultSet;
|
||||
import com.datastax.driver.core.Statement;
|
||||
|
||||
/**
|
||||
* Interface specifying a basic set of CQL asynchronously executed operations. Exposes similar methods as {@link CqlTemplate}, but returns
|
||||
* result handles or accepts callbacks as opposed to concrete results. Implemented by {@link AsyncCqlTemplate}. Not
|
||||
* often used directly, but a useful option to enhance testability, as it can easily be mocked or stubbed.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see AsyncCqlTemplate
|
||||
*/
|
||||
public interface AsyncCqlOperations {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with a plain com.datastax.driver.core.Session
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute a CQL data access operation, implemented as callback action working on a
|
||||
* {@link com.datastax.driver.core.Session}. This allows for implementing arbitrary data access operations, within
|
||||
* Spring's managed CQL environment: that is, converting CQL
|
||||
* {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy.
|
||||
* <p>
|
||||
* The callback action can return a result object, for example a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param action the callback object that specifies the action.
|
||||
* @return a result object returned by the action, or {@literal null}.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
<T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with static CQL
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Issue a single CQL execute, typically a DDL statement, insert, update or delete statement.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @return boolean value whether the statement was applied.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
ListenableFuture<Boolean> execute(String cql) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rse object that will extract all rows of results, must not be {@literal null}.
|
||||
* @return an arbitrary result object, as returned by the ResultSetExtractor.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #query(String, ResultSetExtractor, Object...)
|
||||
*/
|
||||
<T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a
|
||||
* {@link RowCallbackHandler}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code query} method with {@code null} as argument array.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
|
||||
* @throws DataAccessException if there is any problem executing the query
|
||||
* @see #query(String, RowCallbackHandler, Object[])
|
||||
*/
|
||||
ListenableFuture<Void> query(String cql, RowCallbackHandler rch) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @return the result {@link List}, containing mapped objects.
|
||||
* @throws DataAccessException if there is any problem executing the query
|
||||
* @see #query(String, RowMapper, Object[])
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, RowMapper, Object...)} method with
|
||||
* {@literal null} as argument array.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @return the single mapped object.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForObject(String, RowMapper, Object[])
|
||||
*/
|
||||
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result object, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, Class, Object...)} method with
|
||||
* {@literal null} as argument array.
|
||||
* <p>
|
||||
* This method is useful for running static CQL with a known outcome. The query is expected to be a single row/single
|
||||
* column query; the returned result will be directly mapped to the corresponding object type.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
|
||||
* @return the result object of the required type, or {@link Mono#empty()} in case of CQL NULL.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
|
||||
* exactly one column in that row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForObject(String, Class, Object[])
|
||||
*/
|
||||
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result Map, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@link #queryForMap(String, Object...)} method with {@literal null}
|
||||
* as argument array.
|
||||
* <p>
|
||||
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
|
||||
* using the column name as the key).
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @return the result Map (one entry for each column, using the column name as the key), must not be {@literal null}.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForMap(String, Object[])
|
||||
* @see ColumnMapRowMapper
|
||||
*/
|
||||
ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result {@link List}, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
|
||||
* <p>
|
||||
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
|
||||
* specified element type.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
|
||||
* must not be {@literal null}.
|
||||
* @return a {@link List} of objects that match the specified element type.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForList(String, Class, Object[])
|
||||
* @see SingleColumnRowMapper
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result {@link List}, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
|
||||
* <p>
|
||||
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column
|
||||
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
|
||||
* queryForMap() methods.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @return a {@link List} that contains a {@link Map} per row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForList(String, Object[])
|
||||
*/
|
||||
ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a ResultSet, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code queryForResultSet} method with {@literal null} as argument
|
||||
* array.
|
||||
* <p>
|
||||
* The results will be mapped to an {@link ResultSet}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @return a {@link ResultSet} representation.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForResultSet(String, Object[])
|
||||
*/
|
||||
ListenableFuture<ResultSet> queryForResultSet(String cql) throws DataAccessException;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with com.datastax.driver.core.Statement
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Issue a single CQL execute, typically a DDL statement, insert, update or delete statement.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @return boolean value whether the statement was applied.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
ListenableFuture<Boolean> execute(Statement statement) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @param rse object that will extract all rows of results, must not be {@literal null}.
|
||||
* @return an arbitrary result object, as returned by the ResultSetExtractor.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #query(String, ResultSetExtractor, Object...)
|
||||
*/
|
||||
<T> ListenableFuture<T> query(Statement statement, ResultSetExtractor<T> rse) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a
|
||||
* {@link RowCallbackHandler}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code query} method with {@code null} as argument array.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
|
||||
* @throws DataAccessException if there is any problem executing the query
|
||||
* @see #query(String, RowCallbackHandler, Object[])
|
||||
*/
|
||||
ListenableFuture<Void> query(Statement statement, RowCallbackHandler rch) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @return the result {@link List}, containing mapped objects.
|
||||
* @throws DataAccessException if there is any problem executing the query
|
||||
* @see #query(String, RowMapper, Object[])
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> query(Statement statement, RowMapper<T> rowMapper) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, RowMapper, Object...)} method with
|
||||
* {@literal null} as argument array.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @return the single mapped object.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForObject(String, RowMapper, Object[])
|
||||
*/
|
||||
<T> ListenableFuture<T> queryForObject(Statement statement, RowMapper<T> rowMapper) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result object, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, Class, Object...)} method with
|
||||
* {@literal null} as argument array.
|
||||
* <p>
|
||||
* This method is useful for running static CQL with a known outcome. The query is expected to be a single row/single
|
||||
* column query; the returned result will be directly mapped to the corresponding object type.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
|
||||
* @return the result object of the required type, or {@link Mono#empty()} in case of CQL NULL.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
|
||||
* exactly one column in that row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForObject(String, Class, Object[])
|
||||
*/
|
||||
<T> ListenableFuture<T> queryForObject(Statement statement, Class<T> requiredType) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result Map, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@link #queryForMap(String, Object...)} method with {@literal null}
|
||||
* as argument array.
|
||||
* <p>
|
||||
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
|
||||
* using the column name as the key).
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @return the result Map (one entry for each column, using the column name as the key), must not be {@literal null}.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForMap(String, Object[])
|
||||
* @see ColumnMapRowMapper
|
||||
*/
|
||||
ListenableFuture<Map<String, Object>> queryForMap(Statement statement) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result {@link List}, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
|
||||
* <p>
|
||||
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
|
||||
* specified element type.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
|
||||
* must not be {@literal null}.
|
||||
* @return a {@link List} of objects that match the specified element type.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForList(String, Class, Object[])
|
||||
* @see SingleColumnRowMapper
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> queryForList(Statement statement, Class<T> elementType) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a result {@link List}, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
|
||||
* <p>
|
||||
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column
|
||||
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
|
||||
* queryForMap() methods.
|
||||
*
|
||||
* @param statement static CQL {@link Statement} to execute, must not be empty or {@literal null}.
|
||||
* @return a {@link List} that contains a {@link Map} per row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForList(String, Object[])
|
||||
*/
|
||||
ListenableFuture<List<Map<String, Object>>> queryForList(Statement statement) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a ResultSet, given static CQL.
|
||||
* <p>
|
||||
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
|
||||
* {@link PreparedStatement}, use the overloaded {@code queryForResultSet} method with {@literal null} as argument
|
||||
* array.
|
||||
* <p>
|
||||
* The results will be mapped to an {@link ResultSet}.
|
||||
*
|
||||
* @param statement static CQL {@link Statement}, must not be {@literal null}.
|
||||
* @return a {@link ResultSet} representation.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForResultSet(String, Object[])
|
||||
*/
|
||||
ListenableFuture<ResultSet> queryForResultSet(Statement statement) throws DataAccessException;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with prepared statements
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
|
||||
* This allows for implementing arbitrary data access operations on a single {@link PreparedStatement}, within
|
||||
* Spring's managed CQL environment: that is, participating in Spring-managed transactions and converting CQL
|
||||
* {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy.
|
||||
* <p>
|
||||
* The callback action can return a result object, for example a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
|
||||
* must not be {@literal null}.
|
||||
* @param action callback object that specifies the action, must not be {@literal null}.
|
||||
* @return a result object returned by the action, or {@literal null}.
|
||||
* @throws DataAccessException if there is any problem
|
||||
*/
|
||||
<T> ListenableFuture<T> execute(AsyncPreparedStatementCreator psc, PreparedStatementCallback<T> action)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
|
||||
* This allows for implementing arbitrary data access operations on a single Statement, within Spring's managed CQL
|
||||
* environment: that is, participating in Spring-managed transactions and converting CQL
|
||||
* {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy.
|
||||
* <p>
|
||||
* The callback action can return a result object, for example a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param action callback object that specifies the action, must not be {@literal null}.
|
||||
* @return a result object returned by the action, or {@literal null}
|
||||
* @throws DataAccessException if there is any problem TODO: Lambda-usage clashes with execute(cql,
|
||||
* PreparedStatementBinder)
|
||||
*/
|
||||
<T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
|
||||
*
|
||||
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
|
||||
* must not be {@literal null}.
|
||||
* @param rse object that will extract results, must not be {@literal null}.
|
||||
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}
|
||||
* @throws DataAccessException if there is any problem
|
||||
*/
|
||||
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
|
||||
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
|
||||
* set fetch size and other performance options.
|
||||
* @param rse object that will extract results, must not be {@literal null}.
|
||||
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}.
|
||||
* @throws DataAccessException if there is any problem
|
||||
*/
|
||||
<T> ListenableFuture<T> query(String cql, PreparedStatementBinder psb, ResultSetExtractor<T> rse)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
|
||||
* to the query, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
|
||||
*
|
||||
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
|
||||
* must not be {@literal null}.
|
||||
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
|
||||
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
|
||||
* set fetch size and other performance options.
|
||||
* @param rse object that will extract results, must not be {@literal null}.
|
||||
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}.
|
||||
* @throws DataAccessException if there is any problem
|
||||
*/
|
||||
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, ResultSetExtractor<T> rse)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, reading the
|
||||
* {@link ResultSet} with a {@link ResultSetExtractor}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rse object that will extract results, must not be {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type).
|
||||
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
<T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query using a prepared statement, reading the {@link ResultSet} on a per-row basis with a
|
||||
* {@link RowCallbackHandler}.
|
||||
*
|
||||
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
|
||||
* must not be {@literal null}.
|
||||
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a {@link PreparedStatementBinder} implementation that
|
||||
* knows how to bind values to the query, reading the {@link ResultSet} on a per-row basis with a
|
||||
* {@link RowCallbackHandler}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
|
||||
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
|
||||
* set fetch size and other performance options.
|
||||
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
ListenableFuture<Void> query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
|
||||
* to the query, reading the {@link ResultSet} on a per-row basis with a {@link RowCallbackHandler}.
|
||||
*
|
||||
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
|
||||
* must not be {@literal null}.
|
||||
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
|
||||
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
|
||||
* set fetch size and other performance options.
|
||||
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, RowCallbackHandler rch)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, reading the
|
||||
* {@link ResultSet} on a per-row basis with a {@link RowCallbackHandler}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type)
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
ListenableFuture<Void> query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query using a prepared statement, mapping each row to a Java object via a {@link RowMapper}.
|
||||
*
|
||||
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
|
||||
* must not be {@literal null}.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @return the result {@link List}, containing mapped objects.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a {@link PreparedStatementBinder} implementation that
|
||||
* knows how to bind values to the query, mapping each row to a Java object via a {@link RowMapper}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
|
||||
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
|
||||
* set fetch size and other performance options.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @return the result {@link List}, containing mapped objects.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> query(String cql, PreparedStatementBinder psb, RowMapper<T> rowMapper)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
|
||||
* to the query, mapping each row to a Java object via a {@link RowMapper}.
|
||||
*
|
||||
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
|
||||
* must not be {@literal null}.
|
||||
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
|
||||
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
|
||||
* set fetch size and other performance options.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @return the result {@link List}, containing mapped objects.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, RowMapper<T> rowMapper)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, mapping each
|
||||
* row to a Java object via a {@link RowMapper}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rowMapper object that will map one object per row
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type)
|
||||
* @return the result {@link List}, containing mapped objects
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, mapping a
|
||||
* single result row to a Java object via a {@link RowMapper}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param rowMapper object that will map one object per row, must not be {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type)
|
||||
* @return the single mapped object
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
|
||||
* result object.
|
||||
* <p>
|
||||
* The query is expected to be a single row/single column query; the returned result will be directly mapped to the
|
||||
* corresponding object type.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the PreparedStatement to guess the corresponding CQL
|
||||
* type)
|
||||
* @return the result object of the required type, or {@link Mono#empty()} in case of CQL NULL.
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
|
||||
* exactly one column in that row.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForObject(String, Class)
|
||||
*/
|
||||
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
|
||||
* result Map. The queryForMap() methods defined by this interface are appropriate when you don't have a domain model.
|
||||
* Otherwise, consider using one of the queryForObject() methods.
|
||||
* <p>
|
||||
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
|
||||
* using the column name as the key).
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type).
|
||||
* @return the result Map (one entry for each column, using the column name as the key).
|
||||
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForMap(String)
|
||||
* @see ColumnMapRowMapper
|
||||
*/
|
||||
ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
|
||||
* result {@link List}.
|
||||
* <p>
|
||||
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
|
||||
* specified element type.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
|
||||
* must not be {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type).
|
||||
* @return a {@link List} of objects that match the specified element type.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForList(String, Class)
|
||||
* @see SingleColumnRowMapper
|
||||
*/
|
||||
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
|
||||
* result {@link List}.
|
||||
* <p>
|
||||
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column,
|
||||
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
|
||||
* queryForMap() methods.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type).
|
||||
* @return a {@link List} that contains a {@link Map} per row
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForList(String)
|
||||
*/
|
||||
ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
|
||||
* ResultSet.
|
||||
* <p>
|
||||
* The results will be mapped to an {@link ResultSet}.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type).
|
||||
* @return a {@link ResultSet} representation.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
* @see #queryForResultSet(String)
|
||||
*/
|
||||
ListenableFuture<ResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Issue a single CQL execute operation (such as an insert, update or delete statement) using a
|
||||
* {@link AsyncPreparedStatementCreator} to provide CQL and any required parameters.
|
||||
*
|
||||
* @param psc object that provides CQL and any necessary parameters, must not be {@literal null}.
|
||||
* @return boolean value whether the statement was applied.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
// TODO: Interferes with execute(session callback lambda)
|
||||
ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator psc) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Issue an statement using a {@link PreparedStatementBinder} to set bind parameters, with given CQL. Simpler than
|
||||
* using a {@link AsyncPreparedStatementCreator} as this method will create the {@link PreparedStatement}: The
|
||||
* {@link PreparedStatementBinder} just needs to set parameters.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
|
||||
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
|
||||
* set fetch size and other performance options.
|
||||
* @return boolean value whether the statement was applied.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
ListenableFuture<Boolean> execute(String cql, PreparedStatementBinder psb) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Issue a single CQL operation (such as an insert, update or delete statement) via a prepared statement, binding the
|
||||
* given arguments.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
|
||||
* CQL type).
|
||||
* @return boolean value whether the statement was applied.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,973 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.cassandra.support.CassandraAccessor;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.DataAccessUtils;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
|
||||
import com.datastax.driver.core.BoundStatement;
|
||||
import com.datastax.driver.core.ConsistencyLevel;
|
||||
import com.datastax.driver.core.PreparedStatement;
|
||||
import com.datastax.driver.core.ResultSet;
|
||||
import com.datastax.driver.core.Session;
|
||||
import com.datastax.driver.core.SimpleStatement;
|
||||
import com.datastax.driver.core.Statement;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
import com.datastax.driver.core.policies.RetryPolicy;
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
|
||||
/**
|
||||
* <b>This is the central class in the CQL core package for asynchronous Cassandra data access.</b> It simplifies the
|
||||
* use of CQL and helps to avoid common errors. It executes core CQL workflow, leaving application code to provide CQL
|
||||
* and extract results. This class executes CQL queries or updates, initiating iteration over {@link ResultSet}s and
|
||||
* catching {@link DriverException} exceptions and translating them to the generic, more informative exception hierarchy
|
||||
* defined in the {@code org.springframework.dao} package.
|
||||
* <p>
|
||||
* Code using this class need only implement callback interfaces, giving them a clearly defined contract. The
|
||||
* {@link PreparedStatementCreator} callback interface creates a prepared statement given a Connection, providing CQL
|
||||
* and any necessary parameters. The {@link ResultSetExtractor} interface extracts values from a {@link ResultSet}. See
|
||||
* also {@link PreparedStatementBinder} and {@link RowMapper} for two popular alternative callback interfaces.
|
||||
* <p>
|
||||
* Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get
|
||||
* prepared in an application context and given to services as bean reference. Note: The {@link Session} should always
|
||||
* be configured as a bean in the application context, in the first case given to the service directly, in the second
|
||||
* case to the prepared template.
|
||||
* <p>
|
||||
* Because this class is parameterizable by the callback interfaces and the
|
||||
* {@link org.springframework.dao.support.PersistenceExceptionTranslator} interface, there should be no need to subclass
|
||||
* it.
|
||||
* <p>
|
||||
* All CQL operations performed by this class are logged at debug level, using
|
||||
* "org.springframework.cassandra.core.CqlTemplate" as log category.
|
||||
* <p>
|
||||
* <b>NOTE: An instance of this class is thread-safe once configured.</b>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see ListenableFuture
|
||||
* @see PreparedStatementCreator
|
||||
* @see PreparedStatementBinder
|
||||
* @see PreparedStatementCallback
|
||||
* @see ResultSetExtractor
|
||||
* @see RowCallbackHandler
|
||||
* @see RowMapper
|
||||
* @see org.springframework.dao.support.PersistenceExceptionTranslator
|
||||
*/
|
||||
public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOperations {
|
||||
|
||||
/**
|
||||
* Placeholder for default values.
|
||||
*/
|
||||
private final static Statement DEFAULTS = QueryBuilder.select().from("DEFAULT");
|
||||
|
||||
/**
|
||||
* If this variable is set to a non-negative value, it will be used for setting the {@code fetchSize} property on
|
||||
* statements used for query processing.
|
||||
*/
|
||||
private int fetchSize = -1;
|
||||
|
||||
/**
|
||||
* If this variable is set to a value, it will be used for setting the {@code retryPolicy} property on statements used
|
||||
* for query processing.
|
||||
*/
|
||||
private com.datastax.driver.core.policies.RetryPolicy retryPolicy;
|
||||
|
||||
/**
|
||||
* If this variable is set to a value, it will be used for setting the {@code consistencyLevel} property on statements
|
||||
* used for query processing.
|
||||
*/
|
||||
private com.datastax.driver.core.ConsistencyLevel consistencyLevel;
|
||||
|
||||
/**
|
||||
* Construct a new {@link AsyncCqlTemplate}. Note: The {@link Session} has to be set before using the instance.
|
||||
*
|
||||
* @see #setSession(Session)
|
||||
*/
|
||||
public AsyncCqlTemplate() {}
|
||||
|
||||
/**
|
||||
* Construct a new {@link AsyncCqlTemplate}, given a {@link Session}.
|
||||
*
|
||||
* @param session the active Cassandra {@link Session}.
|
||||
*/
|
||||
public AsyncCqlTemplate(Session session) {
|
||||
|
||||
Assert.notNull(session, "Session must not be null");
|
||||
|
||||
setSession(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fetch size for this {@link AsyncCqlTemplate}. This is important for processing large result sets: Setting
|
||||
* this higher than the default value will increase processing speed at the cost of memory consumption; setting this
|
||||
* lower can avoid transferring row data that will never be read by the application. Default is -1, indicating to use
|
||||
* the CQL driver's default configuration (i.e. to not pass a specific fetch size setting on to the driver).
|
||||
*
|
||||
* @see Statement#setFetchSize(int)
|
||||
*/
|
||||
public void setFetchSize(int fetchSize) {
|
||||
this.fetchSize = fetchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fetch size specified for this {@link AsyncCqlTemplate}.
|
||||
*/
|
||||
public int getFetchSize() {
|
||||
return this.fetchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the retry policy for this {@link AsyncCqlTemplate}. This is important for defining behavior when a request
|
||||
* fails.
|
||||
*
|
||||
* @see Statement#setRetryPolicy(com.datastax.driver.core.policies.RetryPolicy)
|
||||
* @see com.datastax.driver.core.policies.RetryPolicy
|
||||
*/
|
||||
public void setRetryPolicy(com.datastax.driver.core.policies.RetryPolicy retryPolicy) {
|
||||
this.retryPolicy = retryPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link com.datastax.driver.core.policies.RetryPolicy} specified for this {@link AsyncCqlTemplate}.
|
||||
*/
|
||||
public com.datastax.driver.core.policies.RetryPolicy getRetryPolicy() {
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the consistency level for this {@link AsyncCqlTemplate}. Consistency level defines the number of nodes involved
|
||||
* into query processing. Relaxed consistency level settings use fewer nodes but eventual consistency is more likely
|
||||
* to occur while a higher consistency level involves more nodes to obtain results with a higher consistency
|
||||
* guarantee.
|
||||
*
|
||||
* @see Statement#setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel)
|
||||
* @see com.datastax.driver.core.policies.RetryPolicy
|
||||
*/
|
||||
public void setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel consistencyLevel) {
|
||||
this.consistencyLevel = consistencyLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link com.datastax.driver.core.ConsistencyLevel} specified for this {@link AsyncCqlTemplate}.
|
||||
*/
|
||||
public com.datastax.driver.core.ConsistencyLevel getConsistencyLevel() {
|
||||
return consistencyLevel;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with a plain com.datastax.driver.core.Session
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncSessionCallback)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException {
|
||||
|
||||
Assert.notNull(action, "Callback object must not be null");
|
||||
|
||||
try {
|
||||
return action.doInSession(getSession());
|
||||
} catch (DriverException e) {
|
||||
throw translateException("SessionCallback", getCql(action), e);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with static CQL
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Boolean> execute(String cql) throws DataAccessException {
|
||||
|
||||
Assert.hasText(cql, "CQL must not be empty");
|
||||
|
||||
return new MappingListenableFutureAdapter<>(queryForResultSet(cql), ResultSet::wasApplied);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse) throws DataAccessException {
|
||||
|
||||
Assert.hasText(cql, "CQL must not be empty");
|
||||
Assert.notNull(rse, "ResultSetExtractor must not be null");
|
||||
|
||||
try {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing CQL Statement [{}]", cql);
|
||||
}
|
||||
|
||||
SimpleStatement simpleStatement = new SimpleStatement(cql);
|
||||
|
||||
applyStatementSettings(simpleStatement);
|
||||
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
|
||||
new GuavaListenableFutureAdapter<>(getSession().executeAsync(simpleStatement),
|
||||
ex -> translateExceptionIfPossible("Query", cql, ex)),
|
||||
rse::extractData), getExceptionTranslator());
|
||||
} catch (DriverException e) {
|
||||
throw translateException("Query", cql, e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Void> query(String cql, RowCallbackHandler rch) throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(query(cql, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
|
||||
getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException {
|
||||
return query(cql, new RowMapperResultSetExtractor<>(rowMapper));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(query(cql, rowMapper), DataAccessUtils::requiredSingleResult),
|
||||
getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException {
|
||||
return queryForObject(cql, getSingleColumnRowMapper(requiredType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException {
|
||||
return queryForObject(cql, getColumnMapRowMapper());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException {
|
||||
return query(cql, getSingleColumnRowMapper(elementType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException {
|
||||
return query(cql, getColumnMapRowMapper());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<ResultSet> queryForResultSet(String cql) throws DataAccessException {
|
||||
return query(cql, rs -> rs);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with com.datastax.driver.core.Statement
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(com.datastax.driver.core.Statement)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Boolean> execute(Statement statement) throws DataAccessException {
|
||||
|
||||
Assert.notNull(statement, "CQL Statement must not be null");
|
||||
|
||||
return new MappingListenableFutureAdapter<>(queryForResultSet(statement), ResultSet::wasApplied);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.ResultSetExtractor)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> query(Statement statement, ResultSetExtractor<T> rse) throws DataAccessException {
|
||||
|
||||
Assert.notNull(statement, "CQL Statement must not be null");
|
||||
Assert.notNull(rse, "ResultSetExtractor must not be null");
|
||||
|
||||
try {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing CQL Statement [{}]", statement);
|
||||
}
|
||||
|
||||
applyStatementSettings(statement);
|
||||
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(new GuavaListenableFutureAdapter<>(getSession().executeAsync(statement),
|
||||
ex -> translateExceptionIfPossible("Query", statement.toString(), ex)), rse::extractData),
|
||||
getExceptionTranslator());
|
||||
} catch (DriverException e) {
|
||||
throw translateException("Query", statement.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowCallbackHandler)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Void> query(Statement statement, RowCallbackHandler rch) throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
|
||||
query(statement, new RowCallbackHandlerResultSetExtractor(rch)), o -> null), getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> query(Statement statement, RowMapper<T> rowMapper) throws DataAccessException {
|
||||
return query(statement, new RowMapperResultSetExtractor<>(rowMapper));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> queryForObject(Statement statement, RowMapper<T> rowMapper)
|
||||
throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(query(statement, rowMapper), DataAccessUtils::requiredSingleResult),
|
||||
getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(com.datastax.driver.core.Statement, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> queryForObject(Statement statement, Class<T> requiredType) throws DataAccessException {
|
||||
return queryForObject(statement, getSingleColumnRowMapper(requiredType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(com.datastax.driver.core.Statement)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Map<String, Object>> queryForMap(Statement statement) throws DataAccessException {
|
||||
return queryForObject(statement, getColumnMapRowMapper());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(com.datastax.driver.core.Statement, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> queryForList(Statement statement, Class<T> elementType)
|
||||
throws DataAccessException {
|
||||
return query(statement, getSingleColumnRowMapper(elementType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(com.datastax.driver.core.Statement)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<List<Map<String, Object>>> queryForList(Statement statement) throws DataAccessException {
|
||||
return query(statement, getColumnMapRowMapper());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(com.datastax.driver.core.Statement)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<ResultSet> queryForResultSet(Statement statement) throws DataAccessException {
|
||||
return query(statement, rs -> rs);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with prepared statements
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementCallback)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> execute(AsyncPreparedStatementCreator psc, PreparedStatementCallback<T> action)
|
||||
throws DataAccessException {
|
||||
|
||||
Assert.notNull(psc, "PreparedStatementCreator must not be null");
|
||||
Assert.notNull(action, "PreparedStatementCallback object must not be null");
|
||||
|
||||
try {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Preparing statement [{}] using {}", getCql(psc), psc);
|
||||
}
|
||||
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(psc.createPreparedStatement(getSession()), preparedStatement -> {
|
||||
|
||||
try {
|
||||
applyStatementSettings(preparedStatement);
|
||||
return action.doInPreparedStatement(preparedStatement);
|
||||
} catch (DriverException e) {
|
||||
throw translateException("PreparedStatementCallback", preparedStatement.toString(), e);
|
||||
}
|
||||
}), getExceptionTranslator());
|
||||
|
||||
} catch (DriverException e) {
|
||||
throw translateException("PreparedStatementCallback", getCql(psc), e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb,
|
||||
ResultSetExtractor<T> rse) throws DataAccessException {
|
||||
|
||||
Assert.notNull(psc, "AsyncPreparedStatementCreator must not be null");
|
||||
Assert.notNull(rse, "ResultSetExtractor object must not be null");
|
||||
|
||||
try {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Preparing statement [{}] using {}", getCql(psc), psc);
|
||||
}
|
||||
|
||||
Session session = getSession();
|
||||
|
||||
PersistenceExceptionTranslator exceptionTranslator = ex -> translateExceptionIfPossible("Query", getCql(psc), ex);
|
||||
|
||||
ListenableFuture<BoundStatement> psFuture = new MappingListenableFutureAdapter<>(
|
||||
psc.createPreparedStatement(session), ps -> {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing prepared statement [{}]", ps);
|
||||
}
|
||||
|
||||
BoundStatement boundStatement = psb != null ? psb.bindValues(ps) : ps.bind();
|
||||
|
||||
applyStatementSettings(boundStatement);
|
||||
|
||||
return boundStatement;
|
||||
});
|
||||
|
||||
SettableListenableFuture<T> settableListenableFuture = new SettableListenableFuture<T>();
|
||||
psFuture.addCallback(boundStatement -> {
|
||||
|
||||
Futures.addCallback(session.executeAsync(boundStatement), new FutureCallback<ResultSet>() {
|
||||
@Override
|
||||
public void onSuccess(ResultSet result) {
|
||||
try {
|
||||
settableListenableFuture.set(rse.extractData(result));
|
||||
} catch (DriverException e) {
|
||||
settableListenableFuture.setException(exceptionTranslator.translateExceptionIfPossible(e));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
|
||||
if (ex instanceof DriverException) {
|
||||
settableListenableFuture
|
||||
.setException(exceptionTranslator.translateExceptionIfPossible((DriverException) ex));
|
||||
} else {
|
||||
settableListenableFuture.setException(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}, ex -> {
|
||||
if (ex instanceof DriverException) {
|
||||
settableListenableFuture.setException(exceptionTranslator.translateExceptionIfPossible((DriverException) ex));
|
||||
} else {
|
||||
settableListenableFuture.setException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
return settableListenableFuture;
|
||||
|
||||
} catch (DriverException e) {
|
||||
throw translateException("Query", getCql(psc), e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementCallback)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException {
|
||||
return execute(newAsyncPreparedStatementCreator(cql), action);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.ResultSetExtractor)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, ResultSetExtractor<T> rse)
|
||||
throws DataAccessException {
|
||||
return query(psc, null, rse);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> query(String cql, PreparedStatementBinder psb, ResultSetExtractor<T> rse)
|
||||
throws DataAccessException {
|
||||
return query(newAsyncPreparedStatementCreator(cql), psb, rse);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse, Object... args)
|
||||
throws DataAccessException {
|
||||
return query(cql, newArgPreparedStatementBinder(args), rse);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.RowCallbackHandler)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, RowCallbackHandler rch)
|
||||
throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(query(psc, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
|
||||
getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Void> query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch)
|
||||
throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(query(cql, psb, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
|
||||
getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb,
|
||||
RowCallbackHandler rch) throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(query(psc, psb, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
|
||||
getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Void> query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(
|
||||
new MappingListenableFutureAdapter<>(query(newAsyncPreparedStatementCreator(cql),
|
||||
newArgPreparedStatementBinder(args), new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
|
||||
getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.RowMapper)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, RowMapper<T> rowMapper)
|
||||
throws DataAccessException {
|
||||
return query(psc, new RowMapperResultSetExtractor<>(rowMapper));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> query(String cql, PreparedStatementBinder psb, RowMapper<T> rowMapper)
|
||||
throws DataAccessException {
|
||||
return query(cql, psb, new RowMapperResultSetExtractor<>(rowMapper));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper)
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb,
|
||||
RowMapper<T> rowMapper) throws DataAccessException {
|
||||
return query(psc, psb, new RowMapperResultSetExtractor<>(rowMapper));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args)
|
||||
throws DataAccessException {
|
||||
return query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args)
|
||||
throws DataAccessException {
|
||||
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
|
||||
query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper, 1)),
|
||||
DataAccessUtils::requiredSingleResult), getExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, java.lang.Class, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args)
|
||||
throws DataAccessException {
|
||||
return queryForObject(cql, getSingleColumnRowMapper(requiredType), args);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(java.lang.String, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException {
|
||||
return queryForObject(cql, getColumnMapRowMapper(), args);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Class, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
|
||||
throws DataAccessException {
|
||||
return query(cql, getSingleColumnRowMapper(elementType), args);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args)
|
||||
throws DataAccessException {
|
||||
return query(cql, getColumnMapRowMapper(), args);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(java.lang.String, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<ResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException {
|
||||
return query(cql, rs -> rs, args);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncPreparedStatementCreator)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator psc) throws DataAccessException {
|
||||
return query(psc, ResultSet::wasApplied);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder)
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Boolean> execute(String cql, PreparedStatementBinder psb) throws DataAccessException {
|
||||
return query(newAsyncPreparedStatementCreator(cql), psb, ResultSet::wasApplied);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException {
|
||||
return execute(cql, newArgPreparedStatementBinder(args));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Implementation hooks and helper methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
|
||||
*
|
||||
* @param task readable text describing the task being attempted
|
||||
* @param cql CQL query or update that caused the problem (may be {@code null})
|
||||
* @param ex the offending {@code RuntimeException}.
|
||||
* @return the translated {@link DataAccessException} or {@literal null} if translation not possible.
|
||||
* @see CqlProvider
|
||||
*/
|
||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
||||
protected DataAccessException translateExceptionIfPossible(String task, String cql, RuntimeException ex) {
|
||||
|
||||
if (ex instanceof DriverException) {
|
||||
return translate(task, cql, (DriverException) ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
|
||||
*
|
||||
* @param task readable text describing the task being attempted
|
||||
* @param cql CQL query or update that caused the problem (may be {@code null})
|
||||
* @param ex the offending {@code RuntimeException}.
|
||||
* @return the exception translation {@link Function}
|
||||
* @see CqlProvider
|
||||
*/
|
||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
||||
protected DataAccessException translateException(String task, String cql, DriverException ex) {
|
||||
return translate(task, cql, ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RowMapper for reading columns as key-value pairs.
|
||||
*
|
||||
* @return the RowMapper to use
|
||||
* @see ColumnMapRowMapper
|
||||
*/
|
||||
protected RowMapper<Map<String, Object>> getColumnMapRowMapper() {
|
||||
return new ColumnMapRowMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RowMapper for reading result objects from a single column.
|
||||
*
|
||||
* @param requiredType the type that each result object is expected to match
|
||||
* @return the RowMapper to use
|
||||
* @see SingleColumnRowMapper
|
||||
*/
|
||||
protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) {
|
||||
return SingleColumnRowMapper.newInstance(requiredType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement
|
||||
* settings such as fetch size, retry policy, and consistency level.
|
||||
*
|
||||
* @param stmt the CQL Statement to prepare
|
||||
* @see #setFetchSize(int)
|
||||
* @see #setRetryPolicy(RetryPolicy)
|
||||
* @see #setConsistencyLevel(ConsistencyLevel)
|
||||
*/
|
||||
protected void applyStatementSettings(Statement stmt) {
|
||||
|
||||
int fetchSize = getFetchSize();
|
||||
if (fetchSize != -1 && stmt.getFetchSize() == DEFAULTS.getFetchSize()) {
|
||||
stmt.setFetchSize(fetchSize);
|
||||
}
|
||||
|
||||
RetryPolicy retryPolicy = getRetryPolicy();
|
||||
if (retryPolicy != null && stmt.getRetryPolicy() == DEFAULTS.getRetryPolicy()) {
|
||||
stmt.setRetryPolicy(retryPolicy);
|
||||
}
|
||||
|
||||
ConsistencyLevel consistencyLevel = getConsistencyLevel();
|
||||
if (consistencyLevel != null && stmt.getConsistencyLevel() == DEFAULTS.getConsistencyLevel()) {
|
||||
stmt.setConsistencyLevel(consistencyLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement
|
||||
* settings such as retry policy and consistency level.
|
||||
*
|
||||
* @param stmt the CQL Statement to prepare
|
||||
* @see #setRetryPolicy(RetryPolicy)
|
||||
* @see #setConsistencyLevel(ConsistencyLevel)
|
||||
*/
|
||||
protected void applyStatementSettings(PreparedStatement stmt) {
|
||||
|
||||
RetryPolicy retryPolicy = getRetryPolicy();
|
||||
if (retryPolicy != null) {
|
||||
stmt.setRetryPolicy(retryPolicy);
|
||||
}
|
||||
|
||||
ConsistencyLevel consistencyLevel = getConsistencyLevel();
|
||||
if (consistencyLevel != null) {
|
||||
stmt.setConsistencyLevel(consistencyLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new arg-based PreparedStatementSetter using the args passed in. By default, we'll create an
|
||||
* {@link ArgumentPreparedStatementBinder}. This method allows for the creation to be overridden by subclasses.
|
||||
*
|
||||
* @param args object array with arguments
|
||||
* @return the new {@link PreparedStatementBinder} to use
|
||||
*/
|
||||
protected PreparedStatementBinder newArgPreparedStatementBinder(Object[] args) {
|
||||
return new ArgumentPreparedStatementBinder(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CQL-based AsyncPreparedStatementCreator using the CQL passed in. By default, we'll create an
|
||||
* {@link SimpleAsyncPreparedStatementCreator}. This method allows for the creation to be overridden by subclasses.
|
||||
*
|
||||
* @param cql static CQL to execute, must not be empty or {@literal null}.
|
||||
* @return the new {@link AsyncPreparedStatementCreator} to use
|
||||
*/
|
||||
protected AsyncPreparedStatementCreator newAsyncPreparedStatementCreator(String cql) {
|
||||
return new SimpleAsyncPreparedStatementCreator(cql,
|
||||
ex -> translateExceptionIfPossible("PrepareStatement", cql, ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine CQL from potential provider object.
|
||||
*
|
||||
* @param cqlProvider object that's potentially a {@link CqlProvider}
|
||||
* @return the CQL string, or {@code null}
|
||||
* @see CqlProvider
|
||||
*/
|
||||
private static String getCql(Object cqlProvider) {
|
||||
|
||||
if (cqlProvider instanceof CqlProvider) {
|
||||
return ((CqlProvider) cqlProvider).getCql();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimpleAsyncPreparedStatementCreator implements AsyncPreparedStatementCreator, CqlProvider {
|
||||
|
||||
private final String cql;
|
||||
private final PersistenceExceptionTranslator persistenceExceptionTranslator;
|
||||
|
||||
SimpleAsyncPreparedStatementCreator(String cql, PersistenceExceptionTranslator persistenceExceptionTranslator) {
|
||||
|
||||
Assert.notNull(cql, "CQL must not be null");
|
||||
|
||||
this.cql = cql;
|
||||
this.persistenceExceptionTranslator = persistenceExceptionTranslator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<PreparedStatement> createPreparedStatement(Session session) throws DriverException {
|
||||
|
||||
return new GuavaListenableFutureAdapter<>(session.prepareAsync(cql), persistenceExceptionTranslator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCql() {
|
||||
return cql;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MappingListenableFutureAdapter<T, S>
|
||||
extends org.springframework.util.concurrent.ListenableFutureAdapter<T, S> {
|
||||
|
||||
private final Function<S, T> mapper;
|
||||
|
||||
public MappingListenableFutureAdapter(ListenableFuture<S> adaptee, Function<S, T> mapper) {
|
||||
super(adaptee);
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T adapt(S adapteeResult) throws ExecutionException {
|
||||
return mapper.apply(adapteeResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter to enable use of a {@link RowCallbackHandler} inside a {@link ResultSetExtractor}.
|
||||
*/
|
||||
private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> {
|
||||
|
||||
private final RowCallbackHandler rch;
|
||||
|
||||
public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
|
||||
this.rch = rch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object extractData(ResultSet rs) {
|
||||
|
||||
StreamSupport.stream(rs.spliterator(), false).forEach(rch::processRow);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
|
||||
import com.datastax.driver.core.PreparedStatement;
|
||||
import com.datastax.driver.core.Session;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* One of the two central callback interfaces used by the {@link AsyncCqlTemplate} class. This interface prepares a CQL
|
||||
* statement returning a {@link org.springframework.util.concurrent.ListenableFuture} given a {@link Session}, provided
|
||||
* by the {@link CqlTemplate} class.
|
||||
* <p>
|
||||
* Implementations may either create new prepared statements or reuse cached instances. Implementations do not need to
|
||||
* concern themselves with {@link DriverException}s that may be thrown from operations they attempt. The
|
||||
* {@link AsyncCqlTemplate} class will catch and handle {@link DriverException}s appropriately.
|
||||
* <p>
|
||||
* A {@link AsyncPreparedStatementCreator} should also implement the {@link CqlProvider} interface if it is able to
|
||||
* provide the CQL it uses for {@link PreparedStatement} creation. This allows for better contextual information in case
|
||||
* of exceptions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface AsyncPreparedStatementCreator {
|
||||
|
||||
/**
|
||||
* Create a statement in this session. Allows implementations to use {@link PreparedStatement}s. The
|
||||
* {@link CqlTemplate} will attempt to cache the {@link PreparedStatement}s for future use without the overhead of
|
||||
* re-preparing on the entire cluster.
|
||||
*
|
||||
* @param session Session to use to create statement, must not be {@literal null}.
|
||||
* @return a prepared statement
|
||||
* @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this
|
||||
* method. The {@link CqlTemplate} class will handle them.
|
||||
*/
|
||||
ListenableFuture<PreparedStatement> createPreparedStatement(Session session) throws DriverException;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
import com.datastax.driver.core.Session;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* Generic callback interface for code that operates asynchronously on a Cassandra {@link Session}. Allows to execute any number of
|
||||
* operations on a single session, using any type and number of statements.
|
||||
* <p>
|
||||
* This is particularly useful for delegating to existing data access code that expects a {@link Session} to work on and
|
||||
* throws {@link DriverException}. For newly written code, it is strongly recommended to use {@link CqlTemplate}'s more
|
||||
* specific operations, for example a {@code query} or {@code update} variant.
|
||||
*
|
||||
* @author David Webb
|
||||
* @author Mark Paluch
|
||||
* @see AsyncCqlTemplate#execute(AsyncSessionCallback)
|
||||
* @see AsyncCqlTemplate#query
|
||||
*/
|
||||
public interface AsyncSessionCallback<T> {
|
||||
|
||||
/**
|
||||
* Gets called by {@link CqlTemplate#execute} with an active Cassandra {@link Session}. Does not need to care about
|
||||
* activating or closing the {@link Session}.
|
||||
* <p>
|
||||
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
|
||||
* objects. Note that there's special support for single step actions: see {@link CqlTemplate#queryForObject} etc. A
|
||||
* thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
|
||||
* template.
|
||||
*
|
||||
* @param session active Cassandra Session, must not be {@literal null}.
|
||||
* @return a result object, or {@code null} if none.
|
||||
* @throws DriverException if thrown by a Session method, to be auto-converted to a {@link DataAccessException}.
|
||||
* @throws DataAccessException in case of custom exceptions.
|
||||
* @see AsyncCqlTemplate#queryForObject(String, Class)
|
||||
* @see AsyncCqlTemplate#queryForResultSet(String)
|
||||
*/
|
||||
ListenableFuture<T> doInSession(Session session) throws DriverException, DataAccessException;
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.concurrent.FailureCallback;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.util.concurrent.SuccessCallback;
|
||||
|
||||
/**
|
||||
* Adapter class to {@link ListenableFuture} {@link ExecutionException} by applying a
|
||||
* {@link PersistenceExceptionTranslator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
class ExceptionTranslatingListenableFutureAdapter<T> implements ListenableFuture<T> {
|
||||
|
||||
private final ListenableFuture<T> adaptee;
|
||||
private final ListenableFuture<T> future;
|
||||
|
||||
/**
|
||||
* Create a new {@link ExceptionTranslatingListenableFutureAdapter} given a {@link ListenableFuture} and a
|
||||
* {@link PersistenceExceptionTranslator}.
|
||||
*
|
||||
* @param adaptee must not be {@literal null}.
|
||||
* @param persistenceExceptionTranslator must not be {@literal null}.
|
||||
*/
|
||||
public ExceptionTranslatingListenableFutureAdapter(ListenableFuture<T> adaptee,
|
||||
PersistenceExceptionTranslator persistenceExceptionTranslator) {
|
||||
|
||||
Assert.notNull(adaptee, "ListenableFuture must not be null");
|
||||
Assert.notNull(persistenceExceptionTranslator, "PersistenceExceptionTranslator must not be null");
|
||||
|
||||
this.adaptee = adaptee;
|
||||
this.future = adaptListenableFuture(adaptee, persistenceExceptionTranslator);
|
||||
}
|
||||
|
||||
private static <T> ListenableFuture adaptListenableFuture(ListenableFuture<T> listenableFuture,
|
||||
PersistenceExceptionTranslator exceptionTranslator) {
|
||||
|
||||
SettableListenableFuture<T> settableFuture = new SettableListenableFuture<T>();
|
||||
|
||||
listenableFuture.addCallback(new ListenableFutureCallback<T>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(T result) {
|
||||
settableFuture.set(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
|
||||
if (ex instanceof RuntimeException) {
|
||||
|
||||
DataAccessException dataAccessException = exceptionTranslator
|
||||
.translateExceptionIfPossible((RuntimeException) ex);
|
||||
if (dataAccessException != null) {
|
||||
settableFuture.setException(dataAccessException);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
settableFuture.setException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
return settableFuture;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.ListenableFutureCallback)
|
||||
*/
|
||||
@Override
|
||||
public void addCallback(ListenableFutureCallback<? super T> callback) {
|
||||
future.addCallback(callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.SuccessCallback, org.springframework.util.concurrent.FailureCallback)
|
||||
*/
|
||||
@Override
|
||||
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
|
||||
future.addCallback(successCallback, failureCallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#cancel(boolean)
|
||||
*/
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return adaptee.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#isCancelled()
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return adaptee.isCancelled();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#isDone()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#get()
|
||||
*/
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException {
|
||||
return future.get();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
|
||||
*/
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return future.get(timeout, unit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.concurrent.FailureCallback;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.util.concurrent.SuccessCallback;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
|
||||
/**
|
||||
* Adapter class to adapt Guava's {@link com.google.common.util.concurrent.ListenableFuture} into a Spring
|
||||
* {@link ListenableFuture}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class GuavaListenableFutureAdapter<T> implements ListenableFuture<T> {
|
||||
|
||||
private final com.google.common.util.concurrent.ListenableFuture<T> adaptee;
|
||||
private final ListenableFuture<T> future;
|
||||
|
||||
/**
|
||||
* Create a new {@link GuavaListenableFutureAdapter} given a Guava
|
||||
* {@link com.google.common.util.concurrent.ListenableFuture} and a {@link PersistenceExceptionTranslator}.
|
||||
*
|
||||
* @param adaptee must not be {@literal null}.
|
||||
* @param persistenceExceptionTranslator must not be {@literal null}.
|
||||
*/
|
||||
public GuavaListenableFutureAdapter(com.google.common.util.concurrent.ListenableFuture<T> adaptee,
|
||||
PersistenceExceptionTranslator persistenceExceptionTranslator) {
|
||||
|
||||
Assert.notNull(adaptee, "ListenableFuture must not be null");
|
||||
Assert.notNull(persistenceExceptionTranslator, "PersistenceExceptionTranslator must not be null");
|
||||
|
||||
this.adaptee = adaptee;
|
||||
this.future = adaptListenableFuture(adaptee, persistenceExceptionTranslator);
|
||||
}
|
||||
|
||||
private static <T> ListenableFuture adaptListenableFuture(
|
||||
com.google.common.util.concurrent.ListenableFuture<T> guavaFuture,
|
||||
PersistenceExceptionTranslator exceptionTranslator) {
|
||||
|
||||
SettableListenableFuture<T> settableFuture = new SettableListenableFuture<T>();
|
||||
|
||||
Futures.addCallback(guavaFuture, new FutureCallback<T>() {
|
||||
@Override
|
||||
public void onSuccess(T result) {
|
||||
settableFuture.set(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
|
||||
if (t instanceof RuntimeException) {
|
||||
|
||||
DataAccessException dataAccessException = exceptionTranslator
|
||||
.translateExceptionIfPossible((RuntimeException) t);
|
||||
if (dataAccessException != null) {
|
||||
settableFuture.setException(dataAccessException);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
settableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return settableFuture;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.ListenableFutureCallback)
|
||||
*/
|
||||
@Override
|
||||
public void addCallback(ListenableFutureCallback<? super T> callback) {
|
||||
future.addCallback(callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.SuccessCallback, org.springframework.util.concurrent.FailureCallback)
|
||||
*/
|
||||
@Override
|
||||
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
|
||||
future.addCallback(successCallback, failureCallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#cancel(boolean)
|
||||
*/
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return adaptee.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#isCancelled()
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return adaptee.isCancelled();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#isDone()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#get()
|
||||
*/
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException {
|
||||
return future.get();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
|
||||
*/
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return future.get(timeout, unit);
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,30 @@
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import com.datastax.driver.core.Host;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* An interface used by {@link CqlTemplate} for mapping {@link Host}s of a {@link com.datastax.driver.core.Metadata} on
|
||||
* a per-item basis.. Implementations of this interface perform the actual work of mapping each host to a result object,
|
||||
* but don't need to worry about exception handling. {@link DriverException} will be caught and handled by the calling
|
||||
* {@link CqlTemplate}.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author Mark Paluch
|
||||
* @see CqlTemplate
|
||||
*/
|
||||
public interface HostMapper<T> {
|
||||
|
||||
Collection<T> mapHosts(Set<Host> host) throws DriverException;
|
||||
|
||||
/**
|
||||
* Implementations must implement this method to map each {@link Host} in the
|
||||
* {@link com.datastax.driver.core.Metadata}.
|
||||
*
|
||||
* @param hosts the {@link Iterable} of {@link Host}s to map, must not be {@literal null}.
|
||||
* @return the result objects for the given hosts.
|
||||
* @throws DriverException if a {@link DriverException} is encountered mapping values (that is, there's no need to
|
||||
* catch {@link DriverException}).
|
||||
*/
|
||||
Collection<T> mapHosts(Iterable<Host> hosts) throws DriverException;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,42 @@ package org.springframework.cassandra.core;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
import com.datastax.driver.core.PreparedStatement;
|
||||
import com.datastax.driver.core.Session;
|
||||
import com.datastax.driver.core.Statement;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* Generic callback interface for code that operates on a {@link PreparedStatement}. Allows to execute any number of
|
||||
* operations on a single {@link PreparedStatement}, for example a single {@link Session#execute(Statement).
|
||||
* <p>
|
||||
* Used internally by {@link CqlTemplate}, but also useful for application code. Note that the passed-in
|
||||
* {@link PreparedStatement} can have been created by the framework or by a custom {@link PreparedStatementCreator}.
|
||||
* However, the latter is hardly ever necessary, as most custom callback actions will perform updates in which case a
|
||||
* standard {@link PreparedStatement is fine. Custom actions will always set parameter values themselves, so that
|
||||
* {@link PreparedStatementCreator} capability is not needed either.
|
||||
*
|
||||
* @author David Webb
|
||||
* @author Mark Paluch
|
||||
* @see CqlTemplate#execute(String, PreparedStatementCallback)
|
||||
* @see CqlTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
|
||||
*/
|
||||
public interface PreparedStatementCallback<T> {
|
||||
|
||||
/**
|
||||
* Gets called by {@link CqlTemplate#execute(String, PreparedStatementCallback)} with a {@link PreparedStatement}.
|
||||
* <p>
|
||||
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
|
||||
* objects. Note that there's special support for single step actions: see
|
||||
* {@link CqlTemplate#queryForObject(String, Class, Object...)} etc. A thrown RuntimeException is treated as
|
||||
* application exception, it gets propagated to the caller of the template.
|
||||
*
|
||||
* @param ps the {@link PreparedStatement}, must not be {@literal null}.
|
||||
* @return a result object publisher.
|
||||
* @throws DriverException if thrown by a session method, to be auto-converted to a DataAccessException.
|
||||
* @throws DataAccessException in case of custom exceptions.
|
||||
* @see CqlTemplate#queryForObject(String, Class, Object...)
|
||||
* @see CqlTemplate#queryForList(String, Object...)
|
||||
*/
|
||||
T doInPreparedStatement(PreparedStatement ps) throws DriverException, DataAccessException;
|
||||
|
||||
}
|
||||
|
||||
@@ -20,21 +20,27 @@ import com.datastax.driver.core.Session;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* Creates a PreparedStatement for the usage with the DataStax Java Driver
|
||||
*
|
||||
* One of the two central callback interfaces used by the {@link CqlTemplate} class. This interface creates a
|
||||
* {@link PreparedStatement} given a session, provided by the {@link CqlTemplate} class. Implementations are responsible
|
||||
* for providing CQL and any necessary parameters.
|
||||
* <p>
|
||||
* Implementations <i>do not</i> need to concern themselves with {@link DriverException}s that may be thrown from
|
||||
* operations they attempt. The {@link CqlTemplate} class will catch and handle {@link DriverException}s appropriately.
|
||||
*
|
||||
* @author David Webb
|
||||
* @author Mark Paluch
|
||||
* @see CqlTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
|
||||
* @see CqlTemplate#query(PreparedStatementCreator, RowCallbackHandler)
|
||||
*/
|
||||
public interface PreparedStatementCreator {
|
||||
|
||||
/**
|
||||
* Create a statement in this session. Allows implementations to use PreparedStatements. The CassandraTemlate will
|
||||
* attempt to cache the PreparedStatement for future use without the overhead of re-preparing on the entire cluster.
|
||||
* Create a statement in this session. Allows implementations to use {@link PreparedStatement}.
|
||||
*
|
||||
* @param session Session to use to create statement
|
||||
* @param session {@link Session} to use to create statement
|
||||
* @return a prepared statement
|
||||
* @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this
|
||||
* method. The CassandraTemlate class will handle them.
|
||||
* @throws DriverException there is no need to catch {@link DriverException} that may be thrown in the implementation
|
||||
* of this method. The {@link CqlTemplate} class will handle them.
|
||||
*/
|
||||
PreparedStatement createPreparedStatement(Session session) throws DriverException;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.datastax.driver.core.PreparedStatement;
|
||||
import com.datastax.driver.core.Statement;
|
||||
import com.datastax.driver.core.querybuilder.Insert;
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
import com.datastax.driver.core.querybuilder.Update;
|
||||
|
||||
/**
|
||||
* Utility class to associate {@link QueryOptions} and {@link WriteOptions} with QueryBuilder {@link Statement}s.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class QueryOptionsUtil {
|
||||
|
||||
/**
|
||||
* Add common {@link QueryOptions} to Cassandra {@link PreparedStatement}s.
|
||||
*
|
||||
* @param preparedStatement the Cassandra {@link PreparedStatement}, must not be {@literal null}.
|
||||
* @param queryOptions query options (e.g. consistency level) to add to the Cassandra {@link PreparedStatement}.
|
||||
*/
|
||||
public static PreparedStatement addPreparedStatementOptions(PreparedStatement preparedStatement,
|
||||
QueryOptions queryOptions) {
|
||||
|
||||
Assert.notNull(preparedStatement, "PreparedStatement must not be null");
|
||||
|
||||
if (queryOptions != null) {
|
||||
if (queryOptions.getDriverConsistencyLevel() != null) {
|
||||
preparedStatement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel());
|
||||
} else if (queryOptions.getConsistencyLevel() != null) {
|
||||
preparedStatement.setConsistencyLevel(ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel()));
|
||||
}
|
||||
|
||||
if (queryOptions.getDriverRetryPolicy() != null) {
|
||||
preparedStatement.setRetryPolicy(queryOptions.getDriverRetryPolicy());
|
||||
} else if (queryOptions.getRetryPolicy() != null) {
|
||||
preparedStatement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy()));
|
||||
}
|
||||
}
|
||||
|
||||
return preparedStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add common {@link QueryOptions} to all types of queries.
|
||||
*
|
||||
* @param statement CQL {@link Statement}, must not be {@literal null}.
|
||||
* @param queryOptions query options (e.g. consistency level) to add to the CQL statement.
|
||||
* @return the given {@link Statement}.
|
||||
*/
|
||||
public static <T extends Statement> T addQueryOptions(T statement, QueryOptions queryOptions) {
|
||||
|
||||
Assert.notNull(statement, "Statement must not be null");
|
||||
|
||||
if (queryOptions != null) {
|
||||
if (queryOptions.getDriverConsistencyLevel() != null) {
|
||||
statement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel());
|
||||
} else if (queryOptions.getConsistencyLevel() != null) {
|
||||
statement.setConsistencyLevel(ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel()));
|
||||
}
|
||||
|
||||
if (queryOptions.getDriverRetryPolicy() != null) {
|
||||
statement.setRetryPolicy(queryOptions.getDriverRetryPolicy());
|
||||
} else if (queryOptions.getRetryPolicy() != null) {
|
||||
statement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy()));
|
||||
}
|
||||
|
||||
if (queryOptions.getFetchSize() != null) {
|
||||
statement.setFetchSize(queryOptions.getFetchSize());
|
||||
}
|
||||
|
||||
if (queryOptions.getReadTimeout() != null) {
|
||||
statement.setReadTimeoutMillis(queryOptions.getReadTimeout().intValue());
|
||||
}
|
||||
|
||||
if (queryOptions.getTracing() != null) {
|
||||
if (queryOptions.getTracing()) {
|
||||
statement.enableTracing();
|
||||
} else {
|
||||
statement.disableTracing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add common {@link WriteOptions} options to {@link Insert} CQL statements.
|
||||
*
|
||||
* @param insert {@link Insert} CQL statement, must not be {@literal null}.
|
||||
* @param writeOptions write options (e.g. consistency level) to add to the CQL statement.
|
||||
* @return the given {@link Insert}.
|
||||
*/
|
||||
public static Insert addWriteOptions(Insert insert, WriteOptions writeOptions) {
|
||||
|
||||
Assert.notNull(insert, "Insert must not be null");
|
||||
|
||||
if (writeOptions != null) {
|
||||
|
||||
addQueryOptions(insert, writeOptions);
|
||||
|
||||
if (writeOptions.getTtl() != null) {
|
||||
insert.using(QueryBuilder.ttl(writeOptions.getTtl()));
|
||||
}
|
||||
}
|
||||
|
||||
return insert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add common {@link WriteOptions} options to {@link Update} CQL statements.
|
||||
*
|
||||
* @param update {@link Update} CQL statement, must not be {@literal null}.
|
||||
* @param writeOptions write options (e.g. consistency level) to add to the CQL statement.
|
||||
* @return the given {@link Update}.
|
||||
*/
|
||||
public static Update addWriteOptions(Update update, WriteOptions writeOptions) {
|
||||
|
||||
Assert.notNull(update, "Update must not be null");
|
||||
|
||||
if (writeOptions != null) {
|
||||
|
||||
addQueryOptions(update, writeOptions);
|
||||
|
||||
if (writeOptions.getTtl() != null) {
|
||||
update.using(QueryBuilder.ttl(writeOptions.getTtl()));
|
||||
}
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,36 @@ import org.springframework.dao.DataAccessException;
|
||||
import com.datastax.driver.core.ResultSet;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* Callback interface used by {@link CqlTemplate}'s query methods. Implementations of this interface perform the actual
|
||||
* work of extracting results from a {@link ResultSet}, but don't need to worry about exception handling.
|
||||
* {@link DriverException}s will be caught and handled by the calling {@link CqlTemplate}.
|
||||
* <p>
|
||||
* This interface is mainly used within the CQL framework itself. A {@link RowMapper} is usually a simpler choice for
|
||||
* {@link ResultSet} processing, mapping one result object per row instead of one result object for the entire
|
||||
* {@link ResultSet}.
|
||||
* <p>
|
||||
* Note: In contrast to a {@link RowCallbackHandler}, a {@link ResultSetExtractor} object is typically stateless and
|
||||
* thus reusable, as long as it doesn't access stateful resources or keep result state within the object.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author Mark Paluch
|
||||
* @since April 24, 2003
|
||||
* @see CqlTemplate
|
||||
* @see RowCallbackHandler
|
||||
* @see RowMapper
|
||||
*/
|
||||
public interface ResultSetExtractor<T> {
|
||||
|
||||
/**
|
||||
* Implementations must implement this method to process the entire {@link ResultSet}.
|
||||
*
|
||||
* @param rs {@link ResultSet} to extract data from.
|
||||
* @return an arbitrary result object, or {@code null} if none (the extractor will typically be stateful in the latter
|
||||
* case).
|
||||
* @throws DriverException if a {@link DriverException} is encountered getting column values or navigating (that is,
|
||||
* there's no need to catch {@link DriverException})
|
||||
* @throws DataAccessException in case of custom exceptions
|
||||
*/
|
||||
T extractData(ResultSet rs) throws DriverException, DataAccessException;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -25,29 +25,26 @@ import com.datastax.driver.core.Host;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* {@link HostMapper} to to map hosts into {@link RingMember} objects.
|
||||
*
|
||||
* @author David Webb
|
||||
* @author Mark Paluch
|
||||
* @param <T>
|
||||
*/
|
||||
public class RingMemberHostMapper implements HostMapper<RingMember> {
|
||||
public enum RingMemberHostMapper implements HostMapper<RingMember> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.HostMapper#mapHosts(java.util.Set)
|
||||
* @see org.springframework.cassandra.core.HostMapper#mapHosts(java.util.Iterable)
|
||||
*/
|
||||
@Override
|
||||
public List<RingMember> mapHosts(Set<Host> hosts) throws DriverException {
|
||||
public Collection<RingMember> mapHosts(Iterable<Host> hosts) throws DriverException {
|
||||
|
||||
List<RingMember> members = new ArrayList<RingMember>();
|
||||
|
||||
Assert.notNull(hosts);
|
||||
Assert.notEmpty(hosts);
|
||||
|
||||
RingMember r = null;
|
||||
for (Host host : hosts) {
|
||||
r = new RingMember(host);
|
||||
members.add(r);
|
||||
}
|
||||
|
||||
return members;
|
||||
Assert.notNull(hosts, "Hosts must not be null");
|
||||
|
||||
return StreamSupport.stream(hosts.spliterator(), false) //
|
||||
.map(RingMember::new) //
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,36 @@ package org.springframework.cassandra.core;
|
||||
import com.datastax.driver.core.Row;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* An interface used by {@link CqlTemplate} for processing rows of a {@link com.datastax.driver.core.ResultSet} on a
|
||||
* per-row basis. Implementations of this interface perform the actual work of processing each row but don't need to
|
||||
* worry about exception handling. {@link DriverException}s will be caught and handled by the calling
|
||||
* {@link CqlTemplate}.
|
||||
* <p>
|
||||
* In contrast to a {@link ResultSetExtractor}, a {@link RowCallbackHandler} object is typically stateful: It keeps the
|
||||
* result state within the object, to be available for later inspection.
|
||||
* <p>
|
||||
* Consider using a {@link RowMapper} instead if you need to map exactly one result object per row, assembling them into
|
||||
* a List.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see CqlTemplate
|
||||
* @see RowMapper
|
||||
* @see ResultSetExtractor
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowCallbackHandler {
|
||||
|
||||
/**
|
||||
* Implementations must implement this method to process each row of data in the {@link ResultSet}. This method is only
|
||||
* supposed to extract values of the current row.
|
||||
* <p>
|
||||
* Exactly what the implementation chooses to do is up to it: A trivial implementation might simply count rows, while
|
||||
* another implementation might build an XML document.
|
||||
*
|
||||
* @param row the {@link Row} to process (pre-initialized for the current row)
|
||||
* @throws DriverException if a {@link DriverException} is encountered getting column values (that is, there's no need
|
||||
* to catch {@link DriverException})
|
||||
*/
|
||||
void processRow(Row row) throws DriverException;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,32 @@ package org.springframework.cassandra.core;
|
||||
import com.datastax.driver.core.Row;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* An interface used by {@link CqlTemplate} for mapping rows of a {@link com.datastax.driver.core.ResultSet} on a
|
||||
* per-row basis. Implementations of this interface perform the actual work of mapping each row to a result object, but
|
||||
* don't need to worry about exception handling. {@link DriverException}s will be caught and handled by the calling
|
||||
* {@link CqlTemplate}.
|
||||
* <p>
|
||||
* Typically used either for {@link CqlTemplate}'s query methods or for out parameters of stored procedures.
|
||||
* {@link RowMapper} objects are typically stateless and thus reusable; they are an ideal choice for implementing
|
||||
* row-mapping logic in a single place.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author Mark Paluch
|
||||
* @see RowCallbackHandler
|
||||
* @see ResultSetExtractor
|
||||
*/
|
||||
public interface RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Implementations must implement this method to map each row of data in the
|
||||
* {@link com.datastax.driver.core.ResultSet}.
|
||||
*
|
||||
* @param row the {@link Row} to map, must not be {@literal null}.
|
||||
* @param rowNum the number of the current row.
|
||||
* @return the result object for the current row.
|
||||
* @throws DriverException if a {@link DriverException} is encountered getting column values (that is, there's no need
|
||||
* to catch {@link DriverException})
|
||||
*/
|
||||
T mapRow(Row row, int rowNum) throws DriverException;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.datastax.driver.core.ResultSet;
|
||||
import com.datastax.driver.core.Row;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* Adapter implementation of the {@link ResultSetExtractor} interface that delegates to a {@link RowMapper} which is
|
||||
* supposed to create an object for each row. Each object is added to the results List of this
|
||||
* {@link ResultSetExtractor}.
|
||||
* <p>
|
||||
* Useful for the typical case of one object per row in the database table. The number of entries in the results will
|
||||
* match the number of rows.
|
||||
* <p>
|
||||
* Note that a {@link RowMapper} object is typically stateless and thus reusable.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see RowMapper
|
||||
* @see CqlTemplate
|
||||
*/
|
||||
public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {
|
||||
|
||||
private final RowMapper<T> rowMapper;
|
||||
|
||||
private final int rowsExpected;
|
||||
|
||||
/**
|
||||
* Create a new {@link RowMapperResultSetExtractor}.
|
||||
*
|
||||
* @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}.
|
||||
*/
|
||||
public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
|
||||
this(rowMapper, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link RowMapperResultSetExtractor}.
|
||||
*
|
||||
* @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}.
|
||||
* @param rowsExpected the number of expected rows (just used for optimized collection handling).
|
||||
*/
|
||||
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
|
||||
|
||||
Assert.notNull(rowMapper, "RowMapper is must not be null");
|
||||
|
||||
this.rowMapper = rowMapper;
|
||||
this.rowsExpected = rowsExpected;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.cassandra.core.ResultSetExtractor#extractData(com.datastax.driver.core.ResultSet)
|
||||
*/
|
||||
@Override
|
||||
public List<T> extractData(ResultSet resultSet) throws DriverException, DataAccessException {
|
||||
|
||||
List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<T>());
|
||||
|
||||
int rowNum = 0;
|
||||
for (Row row : resultSet) {
|
||||
results.add(this.rowMapper.mapRow(row, rowNum++));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -18,22 +18,39 @@ package org.springframework.cassandra.core;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
import com.datastax.driver.core.Session;
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
|
||||
/**
|
||||
* Interface for operations on a Cassandra Session.
|
||||
*
|
||||
* Generic callback interface for code that operates on a Cassandra {@link Session}. Allows to execute any number of
|
||||
* operations on a single session, using any type and number of statements.
|
||||
* <p>
|
||||
* This is particularly useful for delegating to existing data access code that expects a {@link Session} to work on and
|
||||
* throws {@link DriverException}. For newly written code, it is strongly recommended to use {@link CqlTemplate}'s more
|
||||
* specific operations, for example a {@code query} or {@code update} variant.
|
||||
*
|
||||
* @author David Webb
|
||||
* @param <T>
|
||||
* @author Mark Paluch
|
||||
* @see CqlTemplate#execute(SessionCallback)
|
||||
* @see CqlTemplate#query
|
||||
*/
|
||||
public interface SessionCallback<T> {
|
||||
|
||||
/**
|
||||
* Perform the operation in the given Session
|
||||
* Gets called by {@link CqlTemplate#execute} with an active Cassandra {@link Session}. Does not need to care about
|
||||
* activating or closing the {@link Session}.
|
||||
* <p>
|
||||
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
|
||||
* objects. Note that there's special support for single step actions: see {@link CqlTemplate#queryForObject} etc. A
|
||||
* thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
|
||||
* template.
|
||||
*
|
||||
* @param s
|
||||
* @return
|
||||
* @throws DataAccessException
|
||||
* @param session active Cassandra Session, must not be {@literal null}.
|
||||
* @return a result object, or {@code null} if none.
|
||||
* @throws DriverException if thrown by a Session method, to be auto-converted to a {@link DataAccessException}.
|
||||
* @throws DataAccessException in case of custom exceptions.
|
||||
* @see CqlTemplate#queryForObject(String, Class)
|
||||
* @see CqlTemplate#queryForResultSet(String)
|
||||
*/
|
||||
T doInSession(Session s) throws DataAccessException;
|
||||
T doInSession(Session session) throws DriverException, DataAccessException;
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
*/
|
||||
package org.springframework.cassandra.support;
|
||||
|
||||
import com.datastax.driver.core.exceptions.DriverException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.datastax.driver.core.Session;
|
||||
@@ -103,4 +105,50 @@ public class CassandraAccessor implements InitializingBean {
|
||||
Assert.state(this.session != null, "Session was not properly initialized");
|
||||
return this.session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
|
||||
* <p>
|
||||
* The returned {@link DataAccessException} is supposed to contain the original {@code DriverException} as root cause.
|
||||
* However, client code may not generally rely on this due to {@link DataAccessException}s possibly being caused by
|
||||
* other resource APIs as well. That said, a {@code getRootCause() instanceof DataAccessException} check (and
|
||||
* subsequent cast) is considered reliable when expecting Cassandra-based access to have happened.
|
||||
*
|
||||
* @param ex the offending {@link DriverException}
|
||||
* @return the DataAccessException, wrapping the {@code DriverException}
|
||||
* @see <a href=
|
||||
* "http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#dao-exceptions">Consistent
|
||||
* exception hierarchy</a>
|
||||
* @see DataAccessException
|
||||
*/
|
||||
protected DataAccessException translateExceptionIfPossible(DriverException ex) {
|
||||
|
||||
Assert.notNull(ex, "DriverException must not be null");
|
||||
|
||||
return getExceptionTranslator().translateExceptionIfPossible(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
|
||||
* <p>
|
||||
* The returned {@link DataAccessException} is supposed to contain the original {@code DriverException} as root cause.
|
||||
* However, client code may not generally rely on this due to {@link DataAccessException}s possibly being caused by
|
||||
* other resource APIs as well. That said, a {@code getRootCause() instanceof DataAccessException} check (and
|
||||
* subsequent cast) is considered reliable when expecting Cassandra-based access to have happened.
|
||||
*
|
||||
* @param task readable text describing the task being attempted
|
||||
* @param cql CQL query or update that caused the problem (may be {@code null})
|
||||
* @param ex the offending {@link DriverException}
|
||||
* @return the DataAccessException, wrapping the {@code DriverException}
|
||||
* @see org.springframework.dao.DataAccessException#getRootCause()
|
||||
* @see <a href=
|
||||
* "http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#dao-exceptions">Consistent
|
||||
* exception hierarchy</a>
|
||||
*/
|
||||
protected DataAccessException translate(String task, String cql, DriverException ex) {
|
||||
|
||||
Assert.notNull(ex, "DriverException must not be null");
|
||||
|
||||
return getExceptionTranslator().translate(task, cql, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
|
||||
|
||||
import com.datastax.driver.core.PreparedStatement;
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link AsyncCqlTemplate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class AsyncCqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
|
||||
|
||||
private static final AtomicBoolean initialized = new AtomicBoolean();
|
||||
private AsyncCqlTemplate template;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
|
||||
if (initialized.compareAndSet(false, true)) {
|
||||
getSession().execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);");
|
||||
} else {
|
||||
session.execute("TRUNCATE user;");
|
||||
}
|
||||
|
||||
session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');");
|
||||
|
||||
template = new AsyncCqlTemplate();
|
||||
template.setSession(getSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void executeShouldRemoveRecords() throws Exception {
|
||||
|
||||
template.execute("DELETE FROM user WHERE id = 'WHITE'").get();
|
||||
|
||||
assertThat(session.execute("SELECT * FROM user").one()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query("SELECT id FROM user;", row -> {
|
||||
result.add(row.getString(0));
|
||||
}).get();
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectShouldReturnFirstColumn() throws Exception {
|
||||
|
||||
String id = template.queryForObject("SELECT id FROM user;", String.class).get();
|
||||
|
||||
assertThat(id).isEqualTo("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectShouldReturnMap() throws Exception {
|
||||
|
||||
Map<String, Object> map = template.queryForMap("SELECT * FROM user;").get();
|
||||
|
||||
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void executeStatementShouldRemoveRecords() throws Exception {
|
||||
|
||||
template.execute(QueryBuilder.delete().from("user").where(QueryBuilder.eq("id", "WHITE"))).get();
|
||||
|
||||
assertThat(session.execute("SELECT * FROM user").one()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryStatementShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query(QueryBuilder.select("id").from("user"), row -> {
|
||||
result.add(row.getString(0));
|
||||
}).get();
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectStatementShouldReturnFirstColumn() throws Exception {
|
||||
|
||||
String id = template.queryForObject(QueryBuilder.select("id").from("user"), String.class).get();
|
||||
|
||||
assertThat(id).isEqualTo("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectStatementShouldReturnMap() throws Exception {
|
||||
|
||||
Map<String, Object> map = template.queryForMap(QueryBuilder.select().from("user")).get();
|
||||
|
||||
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void executeWithArgsShouldRemoveRecords() throws Exception {
|
||||
|
||||
template.execute("DELETE FROM user WHERE id = ?", "WHITE").get();
|
||||
|
||||
assertThat(session.execute("SELECT * FROM user").one()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryPreparedStatementShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query("SELECT id FROM user WHERE id = ?;", row -> {
|
||||
result.add(row.getString(0));
|
||||
}, "WHITE").get();
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryPreparedStatementCreatorShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query(
|
||||
session -> new GuavaListenableFutureAdapter<PreparedStatement>(
|
||||
session.prepareAsync("SELECT id FROM user WHERE id = ?;"), template.getExceptionTranslator()),
|
||||
ps -> ps.bind("WHITE"), row -> {
|
||||
result.add(row.getString(0));
|
||||
}).get();
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectWithArgsShouldReturnFirstColumn() throws Exception {
|
||||
|
||||
String id = template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE").get();
|
||||
|
||||
assertThat(id).isEqualTo("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectWithArgsShouldReturnMap() throws Exception {
|
||||
|
||||
Map<String, Object> map = template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE").get();
|
||||
|
||||
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
|
||||
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CqlTemplate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
|
||||
|
||||
static final AtomicBoolean initialized = new AtomicBoolean();
|
||||
CqlTemplate template;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
|
||||
if (initialized.compareAndSet(false, true)) {
|
||||
getSession().execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);");
|
||||
} else {
|
||||
session.execute("TRUNCATE user;");
|
||||
}
|
||||
|
||||
session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');");
|
||||
|
||||
template = new CqlTemplate();
|
||||
template.setSession(getSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void executeShouldRemoveRecords() throws Exception {
|
||||
|
||||
template.execute("DELETE FROM user WHERE id = 'WHITE'");
|
||||
|
||||
assertThat(session.execute("SELECT * FROM user").one()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query("SELECT id FROM user;", row -> {
|
||||
result.add(row.getString(0));
|
||||
});
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectShouldReturnFirstColumn() throws Exception {
|
||||
|
||||
String id = template.queryForObject("SELECT id FROM user;", String.class);
|
||||
|
||||
assertThat(id).isEqualTo("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectShouldReturnMap() throws Exception {
|
||||
|
||||
Map<String, Object> map = template.queryForMap("SELECT * FROM user;");
|
||||
|
||||
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void executeStatementShouldRemoveRecords() throws Exception {
|
||||
|
||||
template.execute(QueryBuilder.delete().from("user").where(QueryBuilder.eq("id", "WHITE")));
|
||||
|
||||
assertThat(session.execute("SELECT * FROM user").one()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryStatementShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query(QueryBuilder.select("id").from("user"), row -> {
|
||||
result.add(row.getString(0));
|
||||
});
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectStatementShouldReturnFirstColumn() throws Exception {
|
||||
|
||||
String id = template.queryForObject(QueryBuilder.select("id").from("user"), String.class);
|
||||
|
||||
assertThat(id).isEqualTo("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectStatementShouldReturnMap() throws Exception {
|
||||
|
||||
Map<String, Object> map = template.queryForMap(QueryBuilder.select().from("user"));
|
||||
|
||||
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void executeWithArgsShouldRemoveRecords() throws Exception {
|
||||
|
||||
template.execute("DELETE FROM user WHERE id = ?", "WHITE");
|
||||
|
||||
assertThat(session.execute("SELECT * FROM user").one()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryPreparedStatementShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query("SELECT id FROM user WHERE id = ?;", row -> {
|
||||
result.add(row.getString(0));
|
||||
}, "WHITE");
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryPreparedStatementCreatorShouldInvokeCallback() throws Exception {
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
template.query(session -> session.prepare("SELECT id FROM user WHERE id = ?;"), ps -> ps.bind("WHITE"), row -> {
|
||||
result.add(row.getString(0));
|
||||
});
|
||||
|
||||
assertThat(result).contains("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectWithArgsShouldReturnFirstColumn() throws Exception {
|
||||
|
||||
String id = template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE");
|
||||
|
||||
assertThat(id).isEqualTo("WHITE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-292
|
||||
*/
|
||||
@Test
|
||||
public void queryForObjectWithArgsShouldReturnMap() throws Exception {
|
||||
|
||||
Map<String, Object> map = template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE");
|
||||
|
||||
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
|
||||
}
|
||||
}
|
||||
1202
spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateUnitTests.java
Executable file → Normal file
1202
spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateUnitTests.java
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.core;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.datastax.driver.core.ConsistencyLevel;
|
||||
import com.datastax.driver.core.PreparedStatement;
|
||||
import com.datastax.driver.core.Session;
|
||||
import com.datastax.driver.core.Statement;
|
||||
import com.datastax.driver.core.policies.FallthroughRetryPolicy;
|
||||
import com.datastax.driver.core.querybuilder.Insert;
|
||||
import com.datastax.driver.core.querybuilder.Update;
|
||||
import com.datastax.driver.core.querybuilder.Using;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QueryOptionsUtil}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public class QueryOptionsUtilUnitTests {
|
||||
|
||||
@Rule public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Mock Insert mockInsert;
|
||||
@Mock PreparedStatement mockPreparedStatement;
|
||||
@Mock Session mockSession;
|
||||
@Mock Statement mockStatement;
|
||||
@Mock Update mockUpdate;
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addPreparedStatementOptionsShouldAddDriverQueryOptions() {
|
||||
|
||||
QueryOptions queryOptions = QueryOptions.builder() //
|
||||
.consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
|
||||
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
|
||||
.build();
|
||||
|
||||
QueryOptionsUtil.addPreparedStatementOptions(mockPreparedStatement, queryOptions);
|
||||
|
||||
verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
|
||||
verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addPreparedStatementOptionsShouldAddOurQueryOptions() {
|
||||
|
||||
QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build();
|
||||
|
||||
queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM);
|
||||
|
||||
QueryOptionsUtil.addPreparedStatementOptions(mockPreparedStatement, queryOptions);
|
||||
|
||||
verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
|
||||
verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addStatementQueryOptionsShouldAddDriverQueryOptions() {
|
||||
|
||||
QueryOptions queryOptions = QueryOptions.builder().consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
|
||||
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
|
||||
.build();
|
||||
|
||||
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
|
||||
|
||||
verify(mockStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
|
||||
verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addStatementQueryOptionsShouldAddOurQueryOptions() {
|
||||
|
||||
QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build();
|
||||
|
||||
queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM);
|
||||
|
||||
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
|
||||
|
||||
verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
|
||||
verify(mockStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addStatementQueryOptionsShouldNotAddOptions() {
|
||||
|
||||
QueryOptions queryOptions = QueryOptions.builder().build();
|
||||
|
||||
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
|
||||
|
||||
verifyZeroInteractions(mockStatement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addStatementQueryOptionsShouldAddGenericQueryOptions() {
|
||||
|
||||
QueryOptions queryOptions = QueryOptions.builder() //
|
||||
.fetchSize(10) //
|
||||
.readTimeout(1, TimeUnit.MINUTES) //
|
||||
.withTracing() //
|
||||
.build();
|
||||
|
||||
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
|
||||
|
||||
verify(mockStatement).setReadTimeoutMillis(60 * 1000);
|
||||
verify(mockStatement).setFetchSize(10);
|
||||
verify(mockStatement).enableTracing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addInsertWriteOptionsShouldAddDriverQueryOptions() {
|
||||
|
||||
WriteOptions writeOptions = WriteOptions.builder() //
|
||||
.consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
|
||||
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
|
||||
.readTimeout(10) //
|
||||
.ttl(10) //
|
||||
.build();
|
||||
|
||||
QueryOptionsUtil.addWriteOptions(mockInsert, writeOptions);
|
||||
|
||||
verify(mockInsert).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
|
||||
verify(mockInsert).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
|
||||
verify(mockInsert).setReadTimeoutMillis(10);
|
||||
verify(mockInsert).using(Mockito.any(Using.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACASS-202
|
||||
*/
|
||||
@Test
|
||||
public void addUpdateWriteOptionsShouldAddDriverQueryOptions() {
|
||||
|
||||
WriteOptions writeOptions = WriteOptions.builder() //
|
||||
.consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
|
||||
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
|
||||
.ttl(10) //
|
||||
.tracing(false).build();
|
||||
|
||||
QueryOptionsUtil.addWriteOptions(mockUpdate, writeOptions);
|
||||
|
||||
verify(mockUpdate).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
|
||||
verify(mockUpdate).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
|
||||
verify(mockUpdate).using(Mockito.any(Using.class));
|
||||
verify(mockUpdate).disableTracing();
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,8 @@ public abstract class AbstractEmbeddedCassandraIntegrationTest {
|
||||
@Rule public final CassandraRule cassandraRule = cassandraEnvironment.testInstance()
|
||||
.before(new SessionCallback<Object>() {
|
||||
@Override
|
||||
public Object doInSession(Session s) throws DataAccessException {
|
||||
AbstractEmbeddedCassandraIntegrationTest.this.cluster = s.getCluster();
|
||||
public Object doInSession(Session session) throws DataAccessException {
|
||||
AbstractEmbeddedCassandraIntegrationTest.this.cluster = session.getCluster();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -71,10 +71,10 @@ public abstract class AbstractKeyspaceCreatingIntegrationTest extends AbstractEm
|
||||
cassandraRule.before(new SessionCallback<Object>() {
|
||||
|
||||
@Override
|
||||
public Object doInSession(Session s) throws DataAccessException {
|
||||
public Object doInSession(Session session) throws DataAccessException {
|
||||
|
||||
if (!keyspace.equals(s.getLoggedKeyspace())) {
|
||||
s.execute(String.format("USE %s;", keyspace));
|
||||
if (!keyspace.equals(session.getLoggedKeyspace())) {
|
||||
session.execute(String.format("USE %s;", keyspace));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ public class CassandraRule extends ExternalResource {
|
||||
|
||||
SessionCallback<Void> sessionCallback = new SessionCallback<Void>() {
|
||||
@Override
|
||||
public Void doInSession(Session s) throws DataAccessException {
|
||||
load(s, cqlDataSet);
|
||||
public Void doInSession(Session session) throws DataAccessException {
|
||||
load(session, cqlDataSet);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -180,8 +180,8 @@ public class CassandraRule extends ExternalResource {
|
||||
|
||||
after.add(new SessionCallback<Void>() {
|
||||
@Override
|
||||
public Void doInSession(Session s) throws DataAccessException {
|
||||
load(session, cqlDataSet);
|
||||
public Void doInSession(Session session) throws DataAccessException {
|
||||
load(CassandraRule.this.session, cqlDataSet);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ public class KeyspaceRule extends ExternalResource {
|
||||
} else {
|
||||
cassandraRule.before(new SessionCallback<Object>() {
|
||||
@Override
|
||||
public Object doInSession(Session s) throws DataAccessException {
|
||||
public Object doInSession(Session session) throws DataAccessException {
|
||||
KeyspaceRule.this.session = cassandraRule.getSession();
|
||||
return null;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,422 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.test.integration.core.async;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.cassandra.core.keyspace.CreateTableSpecification.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cassandra.core.*;
|
||||
import org.springframework.cassandra.support.exception.CassandraConnectionFailureException;
|
||||
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
|
||||
import org.springframework.cassandra.test.integration.support.ListOfMapListener;
|
||||
import org.springframework.cassandra.test.integration.support.MapListener;
|
||||
import org.springframework.cassandra.test.integration.support.ObjectListener;
|
||||
import org.springframework.cassandra.test.integration.support.QueryListener;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.Row;
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
import com.datastax.driver.core.querybuilder.Select;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
|
||||
|
||||
public static final String TABLE = "book";
|
||||
CqlOperations cqlOperations;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cqlOperations = new CqlTemplate(session);
|
||||
ensureTableExists();
|
||||
cqlOperations.truncate(TABLE);
|
||||
}
|
||||
|
||||
public static String cql(Book book, String... columns) {
|
||||
if (columns == null || columns.length == 0) {
|
||||
columns = new String[] { "title", "isbn" };
|
||||
}
|
||||
return String.format("select %s from %s where title = '%s' and isbn = '%s'",
|
||||
StringUtils.arrayToCommaDelimitedString(columns), TABLE, book.title, book.isbn);
|
||||
}
|
||||
|
||||
public static String cql(String[] titles) {
|
||||
String[] quoted = new String[titles.length];
|
||||
System.arraycopy(titles, 0, quoted, 0, titles.length);
|
||||
for (int i = 0; i < quoted.length; i++) {
|
||||
quoted[i] = "'" + quoted[i] + "'";
|
||||
}
|
||||
|
||||
return String.format("select * from %s where title in (%s)", TABLE,
|
||||
StringUtils.arrayToCommaDelimitedString(quoted));
|
||||
}
|
||||
|
||||
public static Select select(String isbn) {
|
||||
Select select = QueryBuilder.select("isbn", "title").from(TABLE);
|
||||
select.where(QueryBuilder.eq("isbn", isbn));
|
||||
return select;
|
||||
}
|
||||
|
||||
public static final Comparator<Book> BOOK_COMPARATOR = new Comparator<Book>() {
|
||||
@Override
|
||||
public int compare(Book l, Book r) {
|
||||
return l.isbn.compareTo(r.isbn);
|
||||
}
|
||||
};
|
||||
|
||||
public static final Comparator<? super Map<String, ?>> MAP_WITH_ISBN_COMPARATOR = new Comparator<Map<String, ?>>() {
|
||||
@Override
|
||||
public int compare(Map<String, ?> o1, Map<String, ?> o2) {
|
||||
Assert.isInstanceOf(Comparable.class, o1.get("isbn"),
|
||||
"Map o1 must contain a key 'isbn' and a Comparable value to compare the maps");
|
||||
Assert.isInstanceOf(Comparable.class, o2.get("isbn"),
|
||||
"Map o2 must contain a key 'isbn' and a Comparable value to compare the maps");
|
||||
return ((Comparable) o1.get("isbn")).compareTo(o2.get("isbn"));
|
||||
}
|
||||
};
|
||||
|
||||
public static void assertMapEquals(Map<?, ?> expected, Map<?, ?> actual) {
|
||||
for (Object key : expected.keySet()) {
|
||||
assertThat(actual.containsKey(key)).isTrue();
|
||||
assertThat(actual.get(key)).isEqualTo(expected.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
void ensureTableExists() {
|
||||
cqlOperations.execute(createTable(TABLE).ifNotExists().partitionKeyColumn("title", DataType.ascii())
|
||||
.clusteredKeyColumn("isbn", DataType.ascii()));
|
||||
}
|
||||
|
||||
Book[] insert(int n) {
|
||||
Book[] books = new Book[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
Book b = books[i] = Book.random();
|
||||
cqlOperations.execute(String.format("insert into %s (isbn, title) values ('%s', '%s')", TABLE, b.isbn, b.title));
|
||||
}
|
||||
return books;
|
||||
}
|
||||
|
||||
void assertBook(Book expected, Book actual) {
|
||||
assertThat(actual.isbn).isEqualTo(expected.isbn);
|
||||
assertThat(actual.title).isEqualTo(expected.title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that test {@link AsynchronousQueryListener} should create an anonymous subclass of this class then call
|
||||
* {@link #test()}.
|
||||
*/
|
||||
abstract class AsynchronousQueryListenerTestTemplate {
|
||||
|
||||
/**
|
||||
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
|
||||
* to the appropriate value before returning.
|
||||
*/
|
||||
abstract void doAsyncQuery(Book b, QueryListener listener);
|
||||
|
||||
void test() throws InterruptedException {
|
||||
Book expected = insert(1)[0];
|
||||
QueryListener listener = QueryListener.create();
|
||||
doAsyncQuery(expected, listener);
|
||||
listener.await();
|
||||
Row r = cqlOperations.getResultSetUninterruptibly(listener.getResultSetFuture()).one();
|
||||
Book actual = new Book(r.getString(0), r.getString(1));
|
||||
assertBook(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that test {@link QueryForObjectListener} should create an anonymous subclass of this class then call
|
||||
* {@link #test()}
|
||||
*/
|
||||
abstract class QueryForObjectListenerTestTemplate<T> {
|
||||
|
||||
/**
|
||||
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
|
||||
* to the appropriate value before returning.
|
||||
*/
|
||||
abstract void doAsyncQuery(Book b, QueryForObjectListener<T> listener);
|
||||
|
||||
T expected; // subclass should set this value in doAsyncQuery
|
||||
|
||||
void test() throws Exception {
|
||||
Book book = insert(1)[0];
|
||||
ObjectListener<T> listener = ObjectListener.create();
|
||||
doAsyncQuery(book, listener);
|
||||
listener.await();
|
||||
if (listener.getException() != null) {
|
||||
throw listener.getException();
|
||||
}
|
||||
assertThat(listener.getResult()).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call
|
||||
* {@link #test()}
|
||||
*/
|
||||
abstract class QueryForMapListenerTestTemplate {
|
||||
|
||||
/**
|
||||
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
|
||||
* to the appropriate value before returning.
|
||||
*/
|
||||
abstract void doAsyncQuery(Book b, QueryForMapListener listener);
|
||||
|
||||
Map<String, Object> expected; // subclass should set this value in doAsyncQuery
|
||||
|
||||
void test() throws Exception {
|
||||
Book book = insert(1)[0];
|
||||
MapListener listener = MapListener.create();
|
||||
doAsyncQuery(book, listener);
|
||||
listener.await();
|
||||
if (listener.getException() != null) {
|
||||
throw listener.getException();
|
||||
}
|
||||
assertMapEquals(expected, listener.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call or
|
||||
* {@link #test(int)}
|
||||
*/
|
||||
abstract class QueryForListListenerTestTemplate {
|
||||
|
||||
/**
|
||||
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
|
||||
* to the appropriate value before returning.
|
||||
*/
|
||||
abstract void doAsyncQuery(Book[] books, QueryForListOfMapListener listener);
|
||||
|
||||
List<Map<String, ? extends Comparable>> expected; // subclass should set this value in doAsyncQuery
|
||||
|
||||
void test(int n) throws Exception {
|
||||
Book[] books = insert(n);
|
||||
ListOfMapListener listener = ListOfMapListener.create();
|
||||
Arrays.sort(books, BOOK_COMPARATOR);
|
||||
doAsyncQuery(books, listener);
|
||||
listener.await();
|
||||
if (listener.getException() != null) {
|
||||
throw listener.getException();
|
||||
}
|
||||
|
||||
// sort results the same way as the books array above
|
||||
Collections.sort(listener.getResult(), MAP_WITH_ISBN_COMPARATOR);
|
||||
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
assertMapEquals(expected.get(i), listener.getResult().get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = CancellationException.class)
|
||||
public void testString_AsynchronousQueryListener_Cancelled() throws InterruptedException {
|
||||
new AsynchronousQueryListenerTestTemplate() {
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryListener listener) {
|
||||
Cancellable qc = cqlOperations.queryAsynchronously(cql(b), listener);
|
||||
qc.cancel();
|
||||
}
|
||||
}.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_AsynchronousQueryListener() throws InterruptedException {
|
||||
new AsynchronousQueryListenerTestTemplate() {
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryListener listener) {
|
||||
cqlOperations.queryAsynchronously(cql(b), listener);
|
||||
}
|
||||
}.test();
|
||||
}
|
||||
|
||||
public void testString_AsynchronousQueryListener_QueryOptions(final ConsistencyLevel cl) throws InterruptedException {
|
||||
new AsynchronousQueryListenerTestTemplate() {
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryListener listener) {
|
||||
cqlOperations.queryAsynchronously(cql(b), listener, new QueryOptions(cl, RetryPolicy.DEFAULT));
|
||||
}
|
||||
}.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_AsynchronousQueryListener_QueryOptionsWithConsistencyLevel1() throws InterruptedException {
|
||||
testString_AsynchronousQueryListener_QueryOptions(ConsistencyLevel.ONE);
|
||||
}
|
||||
|
||||
@Test(expected = CassandraConnectionFailureException.class)
|
||||
public void testString_AsynchronousQueryListener_QueryOptionsWithConsistencyLevel2() throws InterruptedException {
|
||||
testString_AsynchronousQueryListener_QueryOptions(ConsistencyLevel.TWO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect_AsynchronousQueryListener() throws InterruptedException {
|
||||
new AsynchronousQueryListenerTestTemplate() {
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryListener listener) {
|
||||
cqlOperations.queryAsynchronously(cql(b), listener);
|
||||
}
|
||||
}.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_QueryForObjectListener() throws Exception {
|
||||
new QueryForObjectListenerTestTemplate<String>() {
|
||||
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryForObjectListener<String> listener) {
|
||||
cqlOperations.queryForObjectAsynchronously(cql(b, "title"), String.class, listener);
|
||||
expected = b.title;
|
||||
}
|
||||
|
||||
}.test();
|
||||
}
|
||||
|
||||
public void testString_QueryForObjectListener_QueryOptions(final ConsistencyLevel cl) throws Exception {
|
||||
new QueryForObjectListenerTestTemplate<String>() {
|
||||
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryForObjectListener<String> listener) {
|
||||
QueryOptions opts = new QueryOptions(cl, RetryPolicy.LOGGING);
|
||||
cqlOperations.queryForObjectAsynchronously(cql(b, "title"), String.class, listener, opts);
|
||||
expected = b.title;
|
||||
}
|
||||
|
||||
}.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_QueryForObjectListener_QueryOptionsWithConsistencyLevel() throws Exception {
|
||||
testString_QueryForObjectListener_QueryOptions(ConsistencyLevel.ONE);
|
||||
}
|
||||
|
||||
@Test(expected = CassandraConnectionFailureException.class)
|
||||
public void testString_QueryForObjectListener_QueryOptionsWithConsistencyLevel2() throws Exception {
|
||||
testString_QueryForObjectListener_QueryOptions(ConsistencyLevel.TWO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_QueryForMapListener() throws Exception {
|
||||
new QueryForMapListenerTestTemplate() {
|
||||
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryForMapListener listener) {
|
||||
cqlOperations.queryForMapAsynchronously(cql(b), listener);
|
||||
expected = new HashMap<String, Object>();
|
||||
expected.put("isbn", b.isbn);
|
||||
expected.put("title", b.title);
|
||||
}
|
||||
|
||||
}.test();
|
||||
}
|
||||
|
||||
public void testString_QueryForMapListener_QueryOptions(final ConsistencyLevel cl) throws Exception {
|
||||
new QueryForMapListenerTestTemplate() {
|
||||
|
||||
@Override
|
||||
void doAsyncQuery(Book b, QueryForMapListener listener) {
|
||||
QueryOptions opts = new QueryOptions(cl, RetryPolicy.LOGGING);
|
||||
cqlOperations.queryForMapAsynchronously(cql(b), listener, opts);
|
||||
expected = new HashMap<String, Object>();
|
||||
expected.put("isbn", b.isbn);
|
||||
expected.put("title", b.title);
|
||||
}
|
||||
|
||||
}.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_QueryForMapListener_QueryOptionsWithConsistencyLevel1() throws Exception {
|
||||
testString_QueryForMapListener_QueryOptions(ConsistencyLevel.ONE);
|
||||
}
|
||||
|
||||
@Test(expected = CassandraConnectionFailureException.class)
|
||||
public void testString_QueryForMapListener_QueryOptionsWithConsistencyLevel2() throws Exception {
|
||||
testString_QueryForMapListener_QueryOptions(ConsistencyLevel.TWO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_QueryForListListener() throws Exception {
|
||||
new QueryForListListenerTestTemplate() {
|
||||
|
||||
@Override
|
||||
void doAsyncQuery(Book[] books, QueryForListOfMapListener listener) {
|
||||
|
||||
String[] titles = new String[books.length];
|
||||
expected = new ArrayList<Map<String, ? extends Comparable>>(books.length);
|
||||
for (int i = 0; i < books.length; i++) {
|
||||
Book b = books[i];
|
||||
titles[i] = b.title;
|
||||
Map<String, String> row = new HashMap<String, String>(2);
|
||||
row.put("title", b.title);
|
||||
row.put("isbn", b.isbn);
|
||||
expected.add(row);
|
||||
}
|
||||
|
||||
cqlOperations.queryForListOfMapAsynchronously(cql(titles), listener);
|
||||
}
|
||||
|
||||
}.test(2);
|
||||
}
|
||||
|
||||
public void testString_QueryForListListener_QueryOptions(final ConsistencyLevel cl) throws Exception {
|
||||
new QueryForListListenerTestTemplate() {
|
||||
|
||||
@Override
|
||||
void doAsyncQuery(Book[] books, QueryForListOfMapListener listener) {
|
||||
|
||||
String[] titles = new String[books.length];
|
||||
expected = new ArrayList<Map<String, ? extends Comparable>>(books.length);
|
||||
for (int i = 0; i < books.length; i++) {
|
||||
Book b = books[i];
|
||||
titles[i] = b.title;
|
||||
Map<String, String> row = new HashMap<String, String>(2);
|
||||
row.put("title", b.title);
|
||||
row.put("isbn", b.isbn);
|
||||
expected.add(row);
|
||||
}
|
||||
|
||||
cqlOperations.queryForListOfMapAsynchronously(cql(titles), listener, new QueryOptions(cl, RetryPolicy.LOGGING));
|
||||
|
||||
}
|
||||
|
||||
}.test(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString_QueryForListListener_QueryOptionsWithConsistencyLevel1() throws Exception {
|
||||
testString_QueryForListListener_QueryOptions(ConsistencyLevel.ONE);
|
||||
}
|
||||
|
||||
@Test(expected = CassandraConnectionFailureException.class)
|
||||
public void testString_QueryForListListener_QueryOptionsWithConsistencyLevel2() throws Exception {
|
||||
testString_QueryForListListener_QueryOptions(ConsistencyLevel.TWO);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.test.integration.core.async;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Matthew T. Adams
|
||||
*/
|
||||
public class Book {
|
||||
|
||||
public static final String uuid() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public static Book random() {
|
||||
return new Book("title-" + uuid(), "isbn-" + uuid());
|
||||
}
|
||||
|
||||
public Book() {}
|
||||
|
||||
public Book(String title, String isbn) {
|
||||
this.isbn = isbn;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String isbn;
|
||||
public String title;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.test.integration.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cassandra.core.QueryForListListener;
|
||||
|
||||
/**
|
||||
* {@link QueryForListListener} suitable for tests.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author David Webb
|
||||
*/
|
||||
public class ListListener<T> extends CallbackSynchronizationSupport implements QueryForListListener<T> {
|
||||
|
||||
private volatile Exception exception;
|
||||
private volatile List<T> result;
|
||||
|
||||
/**
|
||||
* Allow instances only using {@link #create()}
|
||||
*/
|
||||
private ListListener() {}
|
||||
|
||||
/**
|
||||
* @return a new {@link QueryForListListener}.
|
||||
*/
|
||||
public static <T> ListListener<T> create() {
|
||||
return new ListListener<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryComplete(List<T> results) {
|
||||
|
||||
this.result = results;
|
||||
countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception x) {
|
||||
|
||||
this.exception = x;
|
||||
countDown();
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public List<T> getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.test.integration.support;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cassandra.core.QueryForListListener;
|
||||
import org.springframework.cassandra.core.QueryForListOfMapListener;
|
||||
|
||||
/**
|
||||
* {@link QueryForListListener} suitable for tests.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author David Webb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ListOfMapListener extends CallbackSynchronizationSupport implements QueryForListOfMapListener {
|
||||
|
||||
private volatile Exception exception;
|
||||
private volatile List<Map<String, Object>> result;
|
||||
|
||||
/**
|
||||
* Allow instances only using {@link #create()}
|
||||
*/
|
||||
private ListOfMapListener() {}
|
||||
|
||||
/**
|
||||
* @return a new {@link QueryForListListener}.
|
||||
*/
|
||||
public static ListOfMapListener create() {
|
||||
return new ListOfMapListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryComplete(List<Map<String, Object>> results) {
|
||||
|
||||
this.result = results;
|
||||
countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception x) {
|
||||
|
||||
this.exception = x;
|
||||
countDown();
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.test.integration.support;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cassandra.core.QueryForMapListener;
|
||||
|
||||
/**
|
||||
* {@link QueryForMapListener} suitable for tests.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class MapListener extends CallbackSynchronizationSupport implements QueryForMapListener {
|
||||
|
||||
private volatile Map<String, Object> result;
|
||||
private volatile Exception exception;
|
||||
|
||||
/**
|
||||
* Allow instances only using {@link #create()}
|
||||
*/
|
||||
private MapListener() {}
|
||||
|
||||
/**
|
||||
* @return a new {@link MapListener}.
|
||||
*/
|
||||
public static MapListener create() {
|
||||
return new MapListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryComplete(Map<String, Object> results) {
|
||||
|
||||
this.result = results;
|
||||
countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception x) {
|
||||
|
||||
this.exception = x;
|
||||
countDown();
|
||||
}
|
||||
|
||||
public Map<String, Object> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.test.integration.support;
|
||||
|
||||
import org.springframework.cassandra.core.QueryForObjectListener;
|
||||
|
||||
/**
|
||||
* {@link QueryForObjectListener} suitable for tests.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author David Webb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ObjectListener<T> extends CallbackSynchronizationSupport implements QueryForObjectListener<T> {
|
||||
|
||||
private volatile T result;
|
||||
private volatile Exception exception;
|
||||
|
||||
/**
|
||||
* Allow instances only using {@link #create()}
|
||||
*/
|
||||
private ObjectListener() {}
|
||||
|
||||
/**
|
||||
* @return a new {@link ObjectListener}.
|
||||
*/
|
||||
public static <T> ObjectListener<T> create() {
|
||||
return new ObjectListener<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryComplete(T result) {
|
||||
|
||||
this.result = result;
|
||||
countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception x) {
|
||||
|
||||
this.exception = x;
|
||||
countDown();
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cassandra.test.integration.support;
|
||||
|
||||
import org.springframework.cassandra.core.AsynchronousQueryListener;
|
||||
|
||||
import com.datastax.driver.core.ResultSetFuture;
|
||||
|
||||
/**
|
||||
* {@link AsynchronousQueryListener} suitable for usage in tests.
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author David Webb
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class QueryListener extends CallbackSynchronizationSupport implements AsynchronousQueryListener {
|
||||
|
||||
private volatile ResultSetFuture resultSetFuture;
|
||||
|
||||
/**
|
||||
* Allow instances only using {@link #create()}
|
||||
*/
|
||||
private QueryListener() {}
|
||||
|
||||
/**
|
||||
* @return a new {@link QueryListener}.
|
||||
*/
|
||||
public static QueryListener create() {
|
||||
return new QueryListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryComplete(ResultSetFuture resultSetFuture) {
|
||||
|
||||
this.resultSetFuture = resultSetFuture;
|
||||
countDown();
|
||||
}
|
||||
|
||||
public ResultSetFuture getResultSetFuture() {
|
||||
return resultSetFuture;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user