package cassandra;
import cassandra.cql.*;
import cassandra.cql.query.Query;
import cassandra.cql.query.QueryBuilder;
import cassandra.metadata.Metadata;
import cassandra.protocol.CassandraMessage;
import cassandra.retry.RetryContext;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.Promise;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static io.netty.util.internal.PlatformDependent.newConcurrentHashMap;
public class CassandraSession {
private final CassandraCluster.Client cluster;
private final String keyspace;
private final ConcurrentMap<InetAddress, CassandraConnection> connections;
CassandraSession(CassandraCluster.Client cluster) {
this(cluster, "");
}
CassandraSession(CassandraCluster.Client cluster, String keyspace) {
this.cluster = cluster;
this.keyspace = keyspace;
connections = newConcurrentHashMap();
}
public boolean isGlobal() {
return keyspace.isEmpty();
}
public Metadata metadata() {
return cluster.metadata();
}
public CassandraOptions options() {
return cluster.options();
}
public String keyspace() {
return keyspace;
}
public BatchStatement batch() {
return new BatchStatement(this);
}
public Statement statement(String query) {
return new Statement(this, query);
}
public Statement statement(String query, Object... values) {
return new Statement(this, query, values);
}
public PreparedStatement prepareStatement(String query) {
RetryContext context = new RetryContext(options().getRetryPolicy(), options().getRoutingPolicy().activeEndpoints(statement(query)));
CassandraMessage.Request prepare = new CassandraMessage.Prepare(query);
CassandraMessage.Result result = execute(prepare, new ResultFuture(this, context)).get();
CassandraMessage.Result.Prepared prepared = CassandraMessage.Result.Prepared.class.cast(result);
RowMetadata metadata = null;
RowMetadata parameterMetadata = null;
if (prepared.resultMetadata.columns != null) {
metadata = new RowMetadata(prepared.resultMetadata.columns);
}
if (prepared.metadata.columns != null) {
parameterMetadata = new RowMetadata(prepared.metadata.columns);
}
PreparedStatement pstmt = new PreparedStatement(this, prepared.statementId, query, metadata, parameterMetadata);
return cluster.registerPreparedQuery(connection(context.getCurrentEndpoint()), pstmt);
}
public ResultSet execute(Query query) {
return executeAsync(query).get();
}
public ResultSet execute(String query) {
return execute(statement(query));
}
public ResultSet execute(String query, Object... values) {
return execute(statement(query, values));
}
public ResultSet prepareAndExecute(String query, Object... values) {
return execute(prepareStatement(query).bind(values));
}
public ResultSet execute(AbstractStatement<?> statement) {
return executeAsync(statement).get();
}
public ResultSetFuture executeAsync(Query query) {
String keyspace = query.keyspace();
if (keyspace == null || keyspace.isEmpty()) {
if (isGlobal()) {
throw new IllegalArgumentException("empty keyspace");
}
keyspace = this.keyspace;
}
if (!query.hasTable()) {
throw new IllegalArgumentException("empty table");
}
if (!metadata().hasTable(keyspace, query.table())) {
throw new IllegalStateException(String.format("no matching table found: keyspace %s, table %s", keyspace, query.table()));
}
QueryBuilder builder = new QueryBuilder(metadata().getTable(keyspace, query.table()));
query.accept(builder);
AbstractStatement<?> stmt;
if (query.isPrepared() && builder.hasParameters()) {
stmt = prepareStatement(builder.build()).bind(builder.parameters().toArray());
} else {
if (builder.hasParameters()) {
stmt = statement(builder.build(), builder.parameters().toArray());
} else {
stmt = statement(builder.build());
}
}
if (query.pageSizeLimit() > 0) {
stmt.setPageSizeLimit(query.pageSizeLimit());
}
stmt.setKeyspace(query.keyspace());
stmt.setRoutingKey(builder.routingKey());
if (query.routingKey() != null) {
stmt.setRoutingKey(query.routingKey());
}
if (query.routingPolicy() != null) {
stmt.setRoutingPolicy(query.routingPolicy());
}
if (query.retryPolicy() != null) {
stmt.setRetryPolicy(query.retryPolicy());
}
if (query.consistency() != null) {
stmt.setConsistency(query.consistency());
}
if (query.serialConsistency() != null) {
stmt.setSerialConsistency(query.serialConsistency());
}
stmt.setPagingState(query.pagingState());
stmt.setTraceQuery(query.isTracing());
return executeAsync(stmt);
}
public ResultSetFuture executeAsync(String query) {
return executeAsync(statement(query));
}
public ResultSetFuture executeAsync(String query, Object... values) {
return executeAsync(statement(query, values));
}
public ResultSetFuture prepareAndExecuteAsync(String query, Object... values) {
return executeAsync(prepareStatement(query).bind(values));
}
public ResultSetFuture executeAsync(AbstractStatement<?> statement) {
if (statement == null) {
throw new NullPointerException("statement");
}
CassandraMessage.QueryParameters queryParameters = null;
if (!(statement instanceof BatchStatement)) {
RowMetadata metadata = null;
if (statement instanceof PreparedStatement) {
metadata = ((PreparedStatement)statement).getMetadata();
}
PagingState pagingState = statement.getPagingState();
queryParameters = new CassandraMessage.QueryParameters(statement.getConsistency(),
statement.getParameters(),
metadata != null,
statement.getPageSizeLimit(),
pagingState != null ? pagingState.asByteBuffer() : null,
statement.getSerialConsistency());
}
ResultFuture future = executeAsync(statement, queryParameters);
return new ResultSetFuture(future, statement);
}
public ResultFuture executeAsync(AbstractStatement<?> statement, CassandraMessage.QueryParameters queryParameters) {
if (statement == null) {
throw new NullPointerException("statement");
}
CassandraMessage.Request request;
if (statement instanceof BatchStatement) {
BatchStatement batch = (BatchStatement)statement;
List<CassandraMessage.Batch.QueryValue> queryValues = new ArrayList<CassandraMessage.Batch.QueryValue>();
for (AbstractStatement<?> stmt : batch) {
Object stringOrId;
if (stmt instanceof PreparedStatement) {
stringOrId = ((PreparedStatement)stmt).getId();
} else {
stringOrId = stmt.getQuery();
}
List<ByteBuffer> values;
if (stmt.hasParameters()) {
values = Arrays.asList(stmt.getParameters());
} else {
values = Collections.emptyList();
}
queryValues.add(new CassandraMessage.Batch.QueryValue(stringOrId, values));
}
request = new CassandraMessage.Batch(batch.getType(), queryValues, batch.getConsistency());
} else if (statement instanceof PreparedStatement) {
PreparedStatement pstmt = (PreparedStatement)statement;
request = new CassandraMessage.Execute(pstmt.getId(), queryParameters);
} else {
request = new CassandraMessage.Query(statement.getQuery(), queryParameters);
}
request.setTracing(statement.isTraceQuery());
RetryContext context = new RetryContext(statement.getRetryPolicy(), statement.getRoutingPolicy().activeEndpoints(statement));
return execute(request, new ResultFuture(this, context));
}
public void close() {
if (connections != null) {
for (CassandraConnection connection : connections.values()) {
connection.close();
}
connections.clear();
}
}
private ResultFuture execute(CassandraMessage.Request request, ResultFuture future) {
InetAddress endpoint = future.context().getCurrentEndpoint();
connection(endpoint).send(request).addListener(future);
return future;
}
private CassandraConnection connection(InetAddress endpoint) {
if (endpoint == null) {
throw new NullPointerException("endpoint");
}
if (!cluster.isActive()) {
throw new IllegalStateException("cluster not active");
}
CassandraConnection connection = connections.get(endpoint);
if (connection == null) {
CassandraConnection newConnection = cluster.driver().newConnection(endpoint, options());
connection = connections.putIfAbsent(endpoint, newConnection);
if (connection == null) {
connection = newConnection;
}
connection.open(cluster);
if (!isGlobal()) {
connection.keyspace(keyspace);
}
}
if (!connection.isActive()) {
connections.remove(endpoint);
}
return connection;
}
public class ResultFuture implements CassandraFuture.Listener {
private final CassandraSession session;
private final RetryContext context;
private final Promise<CassandraMessage.Result> promise;
ResultFuture(CassandraSession session, RetryContext context) {
this(session, context, new DefaultPromise<CassandraMessage.Result>(GlobalEventExecutor.INSTANCE));
}
ResultFuture(CassandraSession session, RetryContext context, Promise<CassandraMessage.Result> promise) {
this.session = session;
this.context = context;
this.promise = promise;
}
public RetryContext context() {
return context;
}
public Promise<CassandraMessage.Result> promise() {
return promise;
}
public CassandraMessage.Result get() {
return CassandraFuture.get(promise, CassandraFuture.DEADLINE);
}
public CassandraMessage.Result get(long timeout) {
return CassandraFuture.get(promise, timeout);
}
@Override
public void completed(final CassandraFuture future) throws Exception {
if (future.isSuccess()) {
promise.trySuccess((CassandraMessage.Result)future.get());
} else {
Throwable cause = future.cause();
context.setFailure(cause);
if (cause instanceof CassandraException.Unprepared) {
CassandraException.Unprepared unprepared = (CassandraException.Unprepared)cause;
String query = cluster.findPreparedQuery(new PreparedStatement.StatementId(unprepared.id));
if (query != null) {
future.connection().send(new CassandraMessage.Prepare(query)).addListener(new CassandraFuture.Listener() {
@Override
public void completed(CassandraFuture f) throws Exception {
if (f.isSuccess()) {
if (context.canRetry()) {
session.execute(future.request(), ResultFuture.this);
} else {
promise.tryFailure(context.getLastThrowable());
}
} else {
context.setFailure(f.cause());
promise.tryFailure(f.cause());
}
}
});
} else {
promise.tryFailure(cause);
}
} else {
if (context.canRetry()) {
session.execute(future.request(), this);
} else {
promise.tryFailure(cause);
}
}
}
}
}
}