/*
* Copyright 2016-2020 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.core.cql.util.CassandraFutureAdapter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Statement;
/**
* This is the central class in the CQL core package for asynchronous Cassandra data access. 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.
*
* 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 AsyncResultSetExtractor} interface extracts values from a {@link ResultSet}.
* See also {@link PreparedStatementBinder} and {@link RowMapper} for two popular alternative callback interfaces.
*
* Can be used within a service implementation via direct instantiation with a {@link CqlSession} reference, or get
* prepared in an application context and given to services as bean reference. Note: The {@link CqlSession} 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.
*
* 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.
*
* All CQL operations performed by this class are logged at debug level, using
* "org.springframework.data.cassandra.core.cqlTemplate" as log category.
*
* NOTE: An instance of this class is thread-safe once configured.
*
* @author Mark Paluch
* @author John Blum
* @see ListenableFuture
* @see PreparedStatementCreator
* @see PreparedStatementBinder
* @see PreparedStatementCallback
* @see AsyncResultSetExtractor
* @see RowCallbackHandler
* @see RowMapper
* @see org.springframework.dao.support.PersistenceExceptionTranslator
*/
public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOperations {
/**
* Create a new, uninitialized {@link AsyncCqlTemplate}. Note: The {@link SessionFactory} has to be set before using
* the instance.
*
* @see #setSessionFactory(SessionFactory)
*/
public AsyncCqlTemplate() {}
/**
* Create a new {@link AsyncCqlTemplate} with the given {@link CqlSession}.
*
* @param session the active Cassandra {@link CqlSession}, must not be {@literal null}.
* @throws IllegalStateException if {@link CqlSession} is {@literal null}.
*/
public AsyncCqlTemplate(CqlSession session) {
Assert.notNull(session, "Session must not be null");
setSession(session);
}
/**
* Constructs a new {@link AsyncCqlTemplate} with the given {@link SessionFactory}.
*
* @param sessionFactory the active Cassandra {@link SessionFactory}.
* @since 2.0
* @see SessionFactory
*/
public AsyncCqlTemplate(SessionFactory sessionFactory) {
Assert.notNull(sessionFactory, "SessionFactory must not be null");
setSessionFactory(sessionFactory);
}
// -------------------------------------------------------------------------
// Methods dealing with a plain com.datastax.oss.driver.api.core.CqlSession
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#execute(org.springframework.data.cassandra.core.cql.AsyncSessionCallback)
*/
@Override
public ListenableFuture execute(AsyncSessionCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
try {
return action.doInSession(getCurrentSession());
} catch (DriverException e) {
throw translateException("SessionCallback", toCql(action), e);
}
}
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#execute(java.lang.String)
*/
@Override
public ListenableFuture execute(String cql) throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
return new MappingListenableFutureAdapter<>(queryForResultSet(cql), AsyncResultSet::wasApplied);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#query(java.lang.String, org.springframework.data.cassandra.core.cql.AsyncResultSetExtractor)
*/
@Override
public ListenableFuture query(String cql, AsyncResultSetExtractor resultSetExtractor)
throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
Assert.notNull(resultSetExtractor, "AsyncResultSetExtractor must not be null");
try {
if (logger.isDebugEnabled()) {
logger.debug("Executing CQL Statement [{}]", cql);
}
CompletionStage results = getCurrentSession().executeAsync(applyStatementSettings(newStatement(cql)))
.thenApply(resultSetExtractor::extractData) //
.thenCompose(ListenableFuture::completable);
return new CassandraFutureAdapter<>(results, ex -> translateExceptionIfPossible("Query", cql, ex));
} catch (DriverException e) {
throw translateException("Query", cql, e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#query(java.lang.String, org.springframework.data.cassandra.core.cql.RowCallbackHandler)
*/
@Override
public ListenableFuture query(String cql, RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture results = query(cql, newAsyncResultSetExtractor(rowCallbackHandler));
return new MappingListenableFutureAdapter<>(results, o -> null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#query(java.lang.String, org.springframework.data.cassandra.core.cql.RowMapper)
*/
@Override
public ListenableFuture> query(String cql, RowMapper rowMapper) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(rowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#queryForList(java.lang.String)
*/
@Override
public ListenableFuture>> queryForList(String cql) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(newColumnMapRowMapper()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Class)
*/
@Override
public ListenableFuture> queryForList(String cql, Class elementType) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(newSingleColumnRowMapper(elementType)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.AsyncCqlOperations#queryForMap(java.lang.String)
*/
@Override
public ListenableFuture