com.ing.data.cassandra.jdbc.CassandraConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-jdbc-wrapper Show documentation
Show all versions of cassandra-jdbc-wrapper Show documentation
JDBC wrapper of the Java Driver for Apache Cassandra®.
/*
*
* 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 com.ing.data.cassandra.jdbc;
import com.datastax.oss.driver.api.core.ConsistencyLevel;
import com.datastax.oss.driver.api.core.DefaultConsistencyLevel;
import com.datastax.oss.driver.api.core.Version;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.metadata.Metadata;
import com.datastax.oss.driver.api.core.session.Session;
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry;
import com.ing.data.cassandra.jdbc.codec.BigintToBigDecimalCodec;
import com.ing.data.cassandra.jdbc.codec.DecimalToDoubleCodec;
import com.ing.data.cassandra.jdbc.codec.FloatToDoubleCodec;
import com.ing.data.cassandra.jdbc.codec.IntToLongCodec;
import com.ing.data.cassandra.jdbc.codec.LongToIntCodec;
import com.ing.data.cassandra.jdbc.codec.SmallintToIntCodec;
import com.ing.data.cassandra.jdbc.codec.TimestampToLongCodec;
import com.ing.data.cassandra.jdbc.codec.TinyintToIntCodec;
import com.ing.data.cassandra.jdbc.codec.VarintToIntCodec;
import com.ing.data.cassandra.jdbc.optionset.Default;
import com.ing.data.cassandra.jdbc.optionset.OptionSet;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLNonTransientConnectionException;
import java.sql.SQLTimeoutException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_CONCURRENCY;
import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_HOLDABILITY;
import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_TYPE;
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.getDriverProperty;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.ALWAYS_AUTOCOMMIT;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_TIMEOUT;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_TRANSACTIONS;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_CONN;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.PROTOCOL;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_ACTIVE_CQL_VERSION;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_COMPLIANCE_MODE;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONSISTENCY_LEVEL;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CQL_VERSION;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DATABASE_NAME;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DEBUG;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USER;
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.createSubName;
/**
* Cassandra connection: implementation class for {@link Connection} to create a JDBC connection to a Cassandra
* cluster.
*/
public class CassandraConnection extends AbstractConnection implements Connection {
// Minimal Apache Cassandra version supported by the Java Driver for Apache Cassandra® on top which this wrapper is
// built.
// If available, the effective version run by the node on which the connection is established will override these
// values.
/**
* Minimal Apache Cassandra major version supported by the Java Driver for Apache Cassandra®.
*/
public static volatile int dbMajorVersion = 2;
/**
* Minimal Apache Cassandra minor version supported by the Java Driver for Apache Cassandra®.
*/
public static volatile int dbMinorVersion = 1;
/**
* Minimal Apache Cassandra patch version supported by the Java Driver for Apache Cassandra®.
*/
public static volatile int dbPatchVersion = 0;
private static final Logger LOG = LoggerFactory.getLogger(CassandraConnection.class);
private static final boolean AUTO_COMMIT_DEFAULT = true;
/**
* The username used by the connection.
*/
protected String username;
/**
* The connection URL.
*/
protected String url;
private final SessionHolder sessionHolder;
private final Session cSession;
private final Properties connectionProperties;
private final Metadata metadata;
// Set of all the statements that have been created by this connection.
@SuppressWarnings("SortedCollectionWithNonComparableKeys")
private final Set statements = new ConcurrentSkipListSet<>();
private final ConcurrentMap preparedStatements = new ConcurrentHashMap<>();
private final ConsistencyLevel defaultConsistencyLevel;
private String currentKeyspace;
private final boolean debugMode;
private Properties clientInfo;
private volatile boolean isClosed;
private final OptionSet optionSet;
/**
* Instantiates a new JDBC connection to a Cassandra cluster.
*
* @param sessionHolder The session holder.
* @throws SQLException if something went wrong during the initialisation of the connection.
*/
public CassandraConnection(final SessionHolder sessionHolder) throws SQLException {
this.sessionHolder = sessionHolder;
final Properties sessionProperties = sessionHolder.properties;
final DriverExecutionProfile defaultConfigProfile =
sessionHolder.session.getContext().getConfig().getDefaultProfile();
this.debugMode = Boolean.TRUE.toString().equals(sessionProperties.getProperty(TAG_DEBUG, StringUtils.EMPTY));
this.connectionProperties = (Properties) sessionProperties.clone();
this.clientInfo = new Properties();
this.url = PROTOCOL.concat(createSubName(sessionProperties));
this.currentKeyspace = sessionProperties.getProperty(TAG_DATABASE_NAME);
this.optionSet = lookupOptionSet(sessionProperties.getProperty(TAG_COMPLIANCE_MODE));
this.username = sessionProperties.getProperty(TAG_USER,
defaultConfigProfile.getString(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, StringUtils.EMPTY));
final String cqlVersion = sessionProperties.getProperty(TAG_CQL_VERSION,
getDriverProperty("database.defaultCqlVersion"));
this.connectionProperties.setProperty(TAG_ACTIVE_CQL_VERSION, cqlVersion);
this.defaultConsistencyLevel = DefaultConsistencyLevel.valueOf(
sessionProperties.getProperty(TAG_CONSISTENCY_LEVEL,
defaultConfigProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY,
ConsistencyLevel.LOCAL_ONE.name())));
this.cSession = sessionHolder.session;
this.metadata = this.cSession.getMetadata();
LOG.info("Connected to cluster: {}, with session: {}",
Objects.toString(getCatalog(), ""), this.cSession.getName());
this.metadata.getNodes().forEach(
(uuid, node) -> LOG.info("Datacenter: {}; Host: {}; Rack: {}", node.getDatacenter(),
node.getEndPoint().resolve(), node.getRack())
);
this.metadata.getNodes().entrySet().stream().findFirst().ifPresent(entry -> {
final Version cassandraVersion = entry.getValue().getCassandraVersion();
if (cassandraVersion != null) {
CassandraConnection.dbMajorVersion = cassandraVersion.getMajor();
CassandraConnection.dbMinorVersion = cassandraVersion.getMinor();
CassandraConnection.dbPatchVersion = cassandraVersion.getPatch();
LOG.info("Node: {} runs Cassandra v.{}", entry.getValue().getEndPoint().resolve(), cassandraVersion);
}
});
}
/**
* Instantiates a new JDBC connection to a Cassandra cluster using preexisting session.
*
* @param cSession The session to use.
* @param currentKeyspace The keyspace to use.
* @param defaultConsistencyLevel The default consistency level.
* @param debugMode Debug mode flag.
* @param optionSet The compliance mode option set to use.
*/
public CassandraConnection(final Session cSession, final String currentKeyspace,
final ConsistencyLevel defaultConsistencyLevel,
final boolean debugMode, final OptionSet optionSet) {
this.sessionHolder = null;
this.connectionProperties = new Properties();
if (optionSet == null) {
this.optionSet = lookupOptionSet(null);
} else {
this.optionSet = optionSet;
}
this.currentKeyspace = currentKeyspace;
this.cSession = cSession;
this.metadata = cSession.getMetadata();
this.defaultConsistencyLevel = defaultConsistencyLevel;
this.debugMode = debugMode;
final List> codecs = new ArrayList<>();
codecs.add(new TimestampToLongCodec());
codecs.add(new LongToIntCodec());
codecs.add(new IntToLongCodec());
codecs.add(new BigintToBigDecimalCodec());
codecs.add(new DecimalToDoubleCodec());
codecs.add(new FloatToDoubleCodec());
codecs.add(new VarintToIntCodec());
codecs.add(new SmallintToIntCodec());
codecs.add(new TinyintToIntCodec());
codecs.forEach(codec -> ((DefaultCodecRegistry) cSession.getContext().getCodecRegistry()).register(codec));
}
/**
* Checks whether the connection is closed.
*
* @throws SQLException if the connection is closed.
* @throws SQLNonTransientConnectionException if the connection is closed.
*/
private void checkNotClosed() throws SQLException {
if (isClosed()) {
throw new SQLNonTransientConnectionException(WAS_CLOSED_CONN);
}
}
@Override
public void clearWarnings() throws SQLException {
// This implementation does not support the collection of warnings so clearing is a no-op, but it still throws
// an exception when called on a closed connection.
checkNotClosed();
}
@Override
public void close() throws SQLException {
if (this.sessionHolder != null) {
this.sessionHolder.release();
}
this.isClosed = true;
}
@Override
public void commit() throws SQLException {
// Note that Cassandra only supports auto-commit mode, so this is a no-op, but it still throws an exception when
// called on a closed connection.
checkNotClosed();
}
@Override
public java.sql.Statement createStatement() throws SQLException {
checkNotClosed();
final Statement statement = new CassandraStatement(this);
this.statements.add(statement);
return statement;
}
@Override
public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
checkNotClosed();
final Statement statement = new CassandraStatement(this, null, resultSetType, resultSetConcurrency);
this.statements.add(statement);
return statement;
}
@Override
public Statement createStatement(final int resultSetType, final int resultSetConcurrency,
final int resultSetHoldability) throws SQLException {
checkNotClosed();
final Statement statement = new CassandraStatement(this, null, resultSetType, resultSetConcurrency,
resultSetHoldability);
this.statements.add(statement);
return statement;
}
@Override
public boolean getAutoCommit() throws SQLException {
checkNotClosed();
return AUTO_COMMIT_DEFAULT;
}
@Override
public void setAutoCommit(final boolean autoCommit) throws SQLException {
// Note that Cassandra only supports auto-commit mode, so this is a no-op, but it still throws an exception when
// called on a closed connection.
checkNotClosed();
}
@Override
public String getCatalog() throws SQLException {
checkNotClosed();
return this.optionSet.getCatalog();
}
@Override
public void setCatalog(final String catalog) throws SQLException {
// The rationale is there are no catalog name to set in this implementation, so we are "silently ignoring" the
// request, but it still throws an exception when called on closed connection.
checkNotClosed();
}
/**
* Gets a {@link Properties} object listing the properties of this connection.
*
* @return The properties of this connection.
*/
public Properties getConnectionProperties() {
return this.connectionProperties;
}
@Override
public Properties getClientInfo() throws SQLException {
checkNotClosed();
return this.clientInfo;
}
@Override
public String getClientInfo(final String label) throws SQLException {
checkNotClosed();
return this.clientInfo.getProperty(label);
}
@Override
public void setClientInfo(final Properties properties) {
// we don't use them, but we will happily collect them for now...
if (properties != null) {
this.clientInfo = properties;
}
}
@Override
public void setClientInfo(final String key, final String value) {
// we don't use them, but we will happily collect them for now...
this.clientInfo.setProperty(key, value);
}
/**
* Gets the metadata of the Cassandra cluster used by this connection.
*
* @return The metadata of the Cassandra cluster used by this connection.
*/
public Metadata getClusterMetadata() {
return this.metadata;
}
/**
* Gets whether the debug mode is active on this connection.
*
* @return {@code true} if the debug mode is active on this connection, {@code false} otherwise.
*/
public boolean isDebugMode() {
return this.debugMode;
}
/**
* Gets the default consistency level applied to this connection.
*
* @return The default consistency level applied to this connection.
*/
public ConsistencyLevel getDefaultConsistencyLevel() {
return this.defaultConsistencyLevel;
}
@Override
public int getHoldability() throws SQLException {
checkNotClosed();
// The rationale is there are really no commits in Cassandra so no boundary.
return DEFAULT_HOLDABILITY;
}
@Override
public void setHoldability(final int holdability) throws SQLException {
// The rationale is there are no holdability to set in this implementation, so we are "silently ignoring" the
// request, but it still throws an exception when called on closed connection.
checkNotClosed();
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
checkNotClosed();
return new CassandraDatabaseMetaData(this);
}
@Override
public String getSchema() throws SQLException {
checkNotClosed();
return this.currentKeyspace;
}
@Override
public void setSchema(final String schema) throws SQLException {
checkNotClosed();
this.currentKeyspace = schema;
}
/**
* Gets the CQL session used to send requests to the Cassandra cluster.
*
* @return The CQL session.
*/
public Session getSession() {
return this.cSession;
}
@Override
public int getTransactionIsolation() throws SQLException {
checkNotClosed();
return Connection.TRANSACTION_NONE;
}
@Override
public void setTransactionIsolation(final int level) throws SQLException {
checkNotClosed();
if (level != Connection.TRANSACTION_NONE) {
throw new SQLFeatureNotSupportedException(NO_TRANSACTIONS);
}
}
@Override
public Map> getTypeMap() throws SQLException {
final HashMap> typeMap = new HashMap<>();
LOG.info("Current keyspace: {}", this.currentKeyspace);
this.metadata.getKeyspace(this.currentKeyspace)
.ifPresent(keyspaceMetadata ->
keyspaceMetadata.getUserDefinedTypes().forEach((cqlIdentifier, userDefinedType) ->
typeMap.put(cqlIdentifier.asInternal(), userDefinedType.getClass()))
);
return typeMap;
}
@Override
public SQLWarning getWarnings() throws SQLException {
// The rationale is there are no warnings to return in this implementation, so we return null or throw an
// exception when called on closed connection.
checkNotClosed();
return null;
}
@Override
public boolean isClosed() throws SQLException {
return this.isClosed;
}
@Override
public boolean isReadOnly() throws SQLException {
checkNotClosed();
// All the connections are read/write in the Cassandra implementation, so always return false.
return false;
}
@Override
public void setReadOnly(final boolean readOnly) throws SQLException {
// The rationale is all the connections are read/write in the Cassandra implementation, so we are "silently
// ignoring" the request, but it still throws an exception when called on closed connection.
checkNotClosed();
}
@Override
public boolean isValid(final int timeout) throws SQLTimeoutException {
// Throw an exception if the timeout value is invalid.
if (timeout < 0) {
throw new SQLTimeoutException(BAD_TIMEOUT);
}
// Return false if the connection is closed.
if (getSession().isClosed()) {
return false;
}
try (final CassandraStatement stmt = (CassandraStatement) this.createStatement()) {
// Wait at most the defined timeout duration (if requested) for the successful query execution.
final ExecutorService stmtExecutor = Executors.newCachedThreadPool();
final Callable