com.micromux.cassandra.jdbc.CassandraConnection.java Source code

Java tutorial

Introduction

Here is the source code for com.micromux.cassandra.jdbc.CassandraConnection.java

Source

/*
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.micromux.cassandra.jdbc;

import com.datastax.driver.core.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
import java.sql.Connection;
import java.sql.*;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;

import static com.micromux.cassandra.jdbc.CassandraResultSet.DEFAULT_CONCURRENCY;
import static com.micromux.cassandra.jdbc.CassandraResultSet.DEFAULT_HOLDABILITY;
import static com.micromux.cassandra.jdbc.CassandraResultSet.DEFAULT_TYPE;
import static com.micromux.cassandra.jdbc.Utils.*;

/**
 * Implementation class for {@link java.sql.Connection}.
 */
class CassandraConnection extends AbstractConnection implements Connection {

    private static final Logger logger = LoggerFactory.getLogger(CassandraConnection.class);

    public static final int DB_MAJOR_VERSION = 2;
    public static final int DB_MINOR_VERSION = 1;
    public static final String DB_PRODUCT_NAME = "Cassandra";
    public static final String DEFAULT_CQL_VERSION = "3.0.0";

    private final boolean autoCommit = true;

    private final int transactionIsolation = Connection.TRANSACTION_NONE;

    /**
     * Connection Properties
     */
    private Properties connectionProps;

    /**
     * Client Info Properties (currently unused)
     */
    private Properties clientInfo = new Properties();

    /**
     * Set of all Statements that have been created by this connection
     */
    private Set<Statement> statements = new ConcurrentSkipListSet<Statement>();

    /**
     * Cassandra 3.x Session
     */
    private Session session;

    protected String username = null;
    protected String url = null;
    protected String currentKeyspace;//current schema
    protected TreeSet<String> hostListPrimary;
    protected TreeSet<String> hostListBackup;
    int majorCqlVersion;

    PreparedStatement isAlive = null;

    private String currentCqlVersion;

    ConsistencyLevel defaultConsistencyLevel;

    /**
     * Instantiates a new CassandraConnection.
     */
    public CassandraConnection(Properties props) throws SQLException {

        hostListPrimary = new TreeSet<String>();
        hostListBackup = new TreeSet<String>();
        connectionProps = (Properties) props.clone();
        clientInfo = new Properties();

        url = PROTOCOL + createSubName(props);

        String[] hosts = {};
        String host = props.getProperty(TAG_SERVER_NAME);
        int port = Integer.parseInt(props.getProperty(TAG_PORT_NUMBER));

        currentKeyspace = props.getProperty(TAG_DATABASE_NAME);
        username = props.getProperty(TAG_USER);
        String password = props.getProperty(TAG_PASSWORD);
        String version = props.getProperty(TAG_CQL_VERSION, DEFAULT_CQL_VERSION);

        connectionProps.setProperty(TAG_ACTIVE_CQL_VERSION, version);
        defaultConsistencyLevel = ConsistencyLevel
                .valueOf(props.getProperty(TAG_CONSISTENCY_LEVEL, ConsistencyLevel.ONE.name()));

        // take a stab at the CQL version based on what was requested
        majorCqlVersion = getMajor(version);

        // dealing with multiple hosts passed as seeds in the JDBC URL : jdbc:cassandra://lyn4e900.tlt--lyn4e901.tlt--lyn4e902.tlt:9160/fluks
        // in this phase we get the list of all the nodes of the cluster
        String currentHost = "";

        try {

            if (host.contains("--")) {
                hosts = new String[host.split("--").length];
                int i = 0;
                for (String h : host.split("--")) {
                    hosts[i] = h;
                    i++;
                }
            } else {
                hosts = new String[1];
                hosts[0] = host;
            }

            Random rand = new Random();

            currentHost = hosts[rand.nextInt(hosts.length)];
            logger.debug("Chosen seed : " + currentHost);

            Cluster.Builder connectionBuilder = Cluster.builder().addContactPoint(host);

            if (port > 0) {
                connectionBuilder.withPort(port);
            }

            if (!StringUtils.isEmpty(username)) {
                connectionBuilder.withCredentials(username, password);
            }

            // configure SSL if specified
            SSLOptions sslOptions = createSSLOptions(props);

            if (sslOptions != null) {
                connectionBuilder.withSSL(sslOptions);
            }

            Cluster cluster = connectionBuilder.build();
            session = cluster.connect();

            // if keyspace was specified - use it
            if (!session.isClosed() && !StringUtils.isEmpty(currentKeyspace)) {
                session.execute(String.format("USE %s;", currentKeyspace));
            }

            // request version from session
            Configuration configuration = session.getCluster().getConfiguration();
            if ((configuration != null) && (configuration.getProtocolOptions() != null)
                    && (configuration.getProtocolOptions().getProtocolVersionEnum() != null)) {
                ProtocolVersion pv = configuration.getProtocolOptions().getProtocolVersionEnum();
                this.currentCqlVersion = pv.name();

                // recompute the CQL major version from the actual...
                this.majorCqlVersion = pv.toInt();

            }

        } catch (Exception e) {
            String msg = String.format("Connection Fails to %s: %s", currentHost, e.toString());
            logger.error(msg, e);
            throw new SQLException(msg, e);
        }

    }

    private static SSLOptions createSSLOptions(Properties properties) {
        String storePath = properties.getProperty(TAG_TRUST_STORE);
        String storePass = properties.getProperty(TAG_TRUST_PASSWORD);

        if ((storePath != null) && (storePass != null)) {
            SSLContext context;
            try {
                context = getSSLContext(storePath, storePass);

                return new SSLOptions(context, SSLOptions.DEFAULT_SSL_CIPHER_SUITES);

            } catch (UnrecoverableKeyException xk) {
                logger.error("Key Fails for Trust Store: " + storePath, xk);
            } catch (KeyManagementException mx) {
                logger.error("Key Management Error for Trust Store: " + storePath, mx);
            } catch (NoSuchAlgorithmException nx) {
                logger.error("Algorithm Not Found for Trust Store: " + storePath, nx);
            } catch (KeyStoreException kx) {
                logger.error("Key Store Error for Trust Store: " + storePath, kx);
            } catch (CertificateException cx) {
                logger.error("Certificate Error for Trust Store: " + storePath, cx);
            } catch (IOException ix) {
                logger.error("Unable to Read Store Trust Store: " + storePath, ix);
            }

        }

        return null;

    }

    private static SSLContext getSSLContext(String trustPath, String trustPass)
            throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException,
            UnrecoverableKeyException, KeyManagementException {

        FileInputStream tsf = null;
        SSLContext ctx = null;

        try {

            tsf = new FileInputStream(trustPath);
            ctx = SSLContext.getInstance("SSL");

            KeyStore ts = KeyStore.getInstance("JKS");
            ts.load(tsf, trustPass.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(ts);

            ctx.init(null, tmf.getTrustManagers(), new SecureRandom());

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (tsf != null) {
                try {
                    tsf.close();
                } catch (IOException ix) {
                    logger.warn("Error Closing Trust Store: " + trustPath, ix);
                }
            }

        }

        return ctx;

    }

    /**
     * Get the Major portion of a string like : Major.minor.patch where 2 is the default
     */
    private int getMajor(String version) {
        int major = 0;
        String[] parts = version.split("\\.");
        try {
            major = Integer.valueOf(parts[0]);
        } catch (Exception e) {
            major = 2;
        }
        return major;
    }

    private void checkNotClosed() throws SQLException {
        if (isClosed())
            throw new SQLNonTransientConnectionException(WAS_CLOSED_CON);
    }

    public void clearWarnings() throws SQLException {
        // This implementation does not support the collection of warnings so clearing is a no-op
        // but it is still an exception to call this on a closed connection.
        checkNotClosed();
    }

    /**
     * On close of connection.
     */
    public synchronized void close() throws SQLException {
        // close all statements associated with this connection upon close
        for (Statement statement : statements)
            statement.close();
        statements.clear();

        if (isConnected()) {
            // then disconnect from the transport                
            disconnect();
        }
    }

    public void commit() throws SQLException {
        checkNotClosed();
        throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT);
    }

    public Statement createStatement() throws SQLException {
        checkNotClosed();
        Statement statement = new CassandraStatement(this);
        statements.add(statement);
        return statement;
    }

    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        checkNotClosed();
        Statement statement = new CassandraStatement(this, null, resultSetType, resultSetConcurrency);
        statements.add(statement);
        return statement;
    }

    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        checkNotClosed();
        Statement statement = new CassandraStatement(this, null, resultSetType, resultSetConcurrency,
                resultSetHoldability);
        statements.add(statement);
        return statement;
    }

    public boolean getAutoCommit() throws SQLException {
        checkNotClosed();
        return autoCommit;
    }

    public Properties getConnectionProps() {
        return connectionProps;
    }

    /**
     * Retrieves this <code>Connection</code> object's current catalog name.
     * For Cassandra this is treated as the Cluster name.
     *
     * @return the current catalog name or <code>null</code> if there is none
     * @exception SQLException if a database access error occurs
     * or this method is called on a closed connection
     * @see #setCatalog
     */
    public String getCatalog() throws SQLException {
        checkNotClosed();
        return session.getCluster().getClusterName();
    }

    public void setSchema(String schema) throws SQLException {
        checkNotClosed();
        currentKeyspace = schema;
    }

    /**
     * Retrieves this <code>Connection</code> object's current schema name.
     * For Cassandra this is the current <i>keyspace</i>.
     *
     * @return the current schema name or <code>null</code> if there is none
     * @exception SQLException if a database access error occurs
     * or this method is called on a closed connection
     * @see #setSchema
     * @since 1.7
     */
    public String getSchema() throws SQLException {
        checkNotClosed();
        return session.getLoggedKeyspace();
    }

    public Properties getClientInfo() throws SQLException {
        checkNotClosed();
        return clientInfo;
    }

    public String getClientInfo(String label) throws SQLException {
        checkNotClosed();
        return clientInfo.getProperty(label);
    }

    public int getHoldability() throws SQLException {
        checkNotClosed();
        // the rationale is there are really no commits in Cassandra so no boundary...
        return DEFAULT_HOLDABILITY;
    }

    public DatabaseMetaData getMetaData() throws SQLException {
        checkNotClosed();
        return new CassandraDatabaseMetaData(this);
    }

    public int getTransactionIsolation() throws SQLException {
        checkNotClosed();
        return transactionIsolation;
    }

    public SQLWarning getWarnings() throws SQLException {
        checkNotClosed();
        // the rationale is there are no warnings to return in this implementation...
        return null;
    }

    public synchronized boolean isClosed() throws SQLException {

        return !isConnected();
    }

    public boolean isReadOnly() throws SQLException {
        checkNotClosed();
        return false;
    }

    /**
     * Returns true if the connection has not been closed and is still valid.
     * @param timeout  Time in seconds to wait for the database verification operation to complete.
     * @return Result is {@code true} if connection is alive or {@code false} if it tries
     *         to verify and times out.
     * @throws SQLTimeoutException
     */
    @Override
    public boolean isValid(int timeout) throws SQLTimeoutException {
        return ((session != null) && !session.isClosed());
    }

    public boolean isWrapperFor(Class<?> arg0) throws SQLException {
        return false;
    }

    public String nativeSQL(String sql) throws SQLException {
        checkNotClosed();
        // the rationale is there are no distinction between grammars in this implementation...
        // so we are just return the input argument
        return sql;
    }

    public CassandraPreparedStatement prepareStatement(String cql) throws SQLException {
        return prepareStatement(cql, DEFAULT_TYPE, DEFAULT_CONCURRENCY, DEFAULT_HOLDABILITY);
    }

    public CassandraPreparedStatement prepareStatement(String cql, int rsType) throws SQLException {
        return prepareStatement(cql, rsType, DEFAULT_CONCURRENCY, DEFAULT_HOLDABILITY);
    }

    public CassandraPreparedStatement prepareStatement(String cql, int rsType, int rsConcurrency)
            throws SQLException {
        return prepareStatement(cql, rsType, rsConcurrency, DEFAULT_HOLDABILITY);
    }

    public CassandraPreparedStatement prepareStatement(String cql, int rsType, int rsConcurrency, int rsHoldability)
            throws SQLException {
        checkNotClosed();
        CassandraPreparedStatement statement = new CassandraPreparedStatement(this, cql, rsType, rsConcurrency,
                rsHoldability);
        statements.add(statement);
        return statement;
    }

    public void rollback() throws SQLException {
        checkNotClosed();
        throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT);
    }

    public void setAutoCommit(boolean autoCommit) throws SQLException {
        checkNotClosed();
        if (!autoCommit)
            throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT);
    }

    public void setCatalog(String arg0) throws SQLException {
        checkNotClosed();
        // the rationale is there are no catalog name to set in this implementation...
        // so we are "silently ignoring" the request
    }

    public void setClientInfo(Properties props) throws SQLClientInfoException {
        // we don't use them but we will happily collect them for now...
        if (props != null)
            clientInfo = props;
    }

    public void setClientInfo(String key, String value) throws SQLClientInfoException {
        // we don't use them but we will happily collect them for now...
        clientInfo.setProperty(key, value);
    }

    public void setHoldability(int arg0) throws SQLException {
        checkNotClosed();
        // the rationale is there are no holdability to set in this implementation...
        // so we are "silently ignoring" the request
    }

    public void setReadOnly(boolean arg0) throws SQLException {
        checkNotClosed();
        // the rationale is all connections are read/write in the Cassandra implementation...
        // so we are "silently ignoring" the request
    }

    public void setTransactionIsolation(int level) throws SQLException {
        checkNotClosed();
        if (level != Connection.TRANSACTION_NONE)
            throw new SQLFeatureNotSupportedException(NO_TRANSACTIONS);
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        throw new SQLFeatureNotSupportedException(String.format(NO_INTERFACE, iface.getSimpleName()));
    }

    /**
     * Execute a CQL query.
     *
     * @param queryStr    a CQL query string
     */
    protected com.datastax.driver.core.ResultSet execute(String queryStr, ConsistencyLevel consistencyLevel) {
        return session.execute(queryStr);
    }

    protected com.datastax.driver.core.PreparedStatement prepare(String queryStr) {
        return session.prepare(queryStr);
    }

    /**
     * Remove a Statement from the Open Statements List
     */
    protected boolean removeStatement(Statement statement) {
        return statements.remove(statement);
    }

    /**
     * Shutdown the remote connection
     */
    protected void disconnect() {
        session.close();
    }

    /**
     * Connection state.
     */
    protected boolean isConnected() {
        return ((session != null) && !session.isClosed());
    }

    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("CassandraConnection [connectionProps=");
        builder.append(connectionProps);
        builder.append("]");
        return builder.toString();
    }

    protected final com.datastax.driver.core.ResultSet execute(BoundStatement boundStatement) {
        return session.execute(boundStatement);
    }
}