package me.prettyprint.cassandra.connection.client; import java.net.SocketException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import me.prettyprint.cassandra.service.CassandraHost; import me.prettyprint.cassandra.service.ExceptionsTranslator; import me.prettyprint.cassandra.service.ExceptionsTranslatorImpl; import me.prettyprint.cassandra.service.SystemProperties; import me.prettyprint.hector.api.exceptions.HInvalidRequestException; import me.prettyprint.hector.api.exceptions.HectorTransportException; import org.apache.cassandra.thrift.Cassandra; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.commons.lang.StringUtils; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSSLTransportFactory; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; import org.apache.thrift.transport.TSSLTransportFactory.TSSLTransportParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* * It expects few system properties to be set up if it uses SSL: * <ul> * <li><code>ssl.truststore</code> File path for trust store * <li><code>ssl.truststore.password</code> Password for trust store * <li><code>ssl.protocol</code> SSL protocol, default SSL * <li><code>ssl.store.type</code> Store type, default JKS * <li><code>ssl.cipher.suites</code> Cipher suites * </ul> * <p> */ public class HThriftClient implements HClient { private long createdTime = System.currentTimeMillis(); private static Logger log = LoggerFactory.getLogger(HThriftClient.class); private static final String NAME_FORMAT = "CassandraClient<%s-%d>"; private static final AtomicLong serial = new AtomicLong(0); final CassandraHost cassandraHost; final ExceptionsTranslator exceptionsTranslator; private final long mySerial; protected final int timeout; protected String keyspaceName; private long useageStartTime; protected TTransport transport; protected Cassandra.Client cassandraClient; private TSSLTransportParameters params; private volatile long lastSuccessTime; private final Map<String, String> credentials = new HashMap<String, String>(); /** * Constructor * @param cassandraHost */ public HThriftClient(CassandraHost cassandraHost) { this.cassandraHost = cassandraHost; this.timeout = getTimeout(cassandraHost); mySerial = serial.incrementAndGet(); exceptionsTranslator = new ExceptionsTranslatorImpl(); } /** * Constructor * @param cassandraHost * @param params */ public HThriftClient(CassandraHost cassandraHost, TSSLTransportParameters params) { this.cassandraHost = cassandraHost; this.timeout = getTimeout(cassandraHost); this.params = params; mySerial = serial.incrementAndGet(); exceptionsTranslator = new ExceptionsTranslatorImpl(); } /** * {@inheritDoc} */ public Cassandra.Client getCassandra() { if ( !isOpen() ) { throw new IllegalStateException("getCassandra called on client that was not open. You should not have gotten here."); } if ( cassandraClient == null ) { cassandraClient = new Cassandra.Client(new TBinaryProtocol(transport)); } return cassandraClient; } /** * {@inheritDoc} */ public Cassandra.Client getCassandra(String keyspaceNameArg) { getCassandra(); if ( keyspaceNameArg != null && !StringUtils.equals(keyspaceName, keyspaceNameArg)) { if ( log.isDebugEnabled() ) log.debug("keyspace reseting from {} to {}", keyspaceName, keyspaceNameArg); try { cassandraClient.set_keyspace(keyspaceNameArg); } catch (InvalidRequestException ire) { throw new HInvalidRequestException(ire); } catch (TException e) { throw exceptionsTranslator.translate(e); } keyspaceName = keyspaceNameArg; } return cassandraClient; } /** * {@inheritDoc} */ public HThriftClient close() { if ( log.isDebugEnabled() ) { log.debug("Closing client {}", this); } if ( isOpen() ) { try { transport.flush(); } catch (Exception e) { log.error("Could not flush transport (to be expected if the pool is shutting down) in close for client: " + toString(), e); } finally { try { transport.close(); } catch (Exception e) { log.error("Error on transport close for client: " +toString(), e); } } } return this; } /** * {@inheritDoc} */ public HThriftClient open() { if ( isOpen() ) { throw new IllegalStateException("Open called on already open connection. You should not have gotten here."); } if ( log.isDebugEnabled() ) { log.debug("Creating a new thrift connection to {}", cassandraHost); } TSocket socket; try { socket = params == null ? new TSocket(cassandraHost.getHost(), cassandraHost.getPort(), timeout) : TSSLTransportFactory.getClientSocket(cassandraHost.getHost(), cassandraHost.getPort(), timeout, params); } catch (TTransportException e) { throw new HectorTransportException("Could not get client socket: ", e); } if ( cassandraHost.getUseSocketKeepalive() ) { try { socket.getSocket().setKeepAlive(true); } catch (SocketException se) { throw new HectorTransportException("Could not set SO_KEEPALIVE on socket: ", se); } } transport = maybeWrapWithTFramedTransport(socket); // If using SSL, the socket will already be connected, and TFramedTransport and // TSocket just wind up calling socket.isConnected(), so check this before calling // open() to avoid a "Socket already connected" error. if (!transport.isOpen()) { try { transport.open(); } catch (TTransportException e) { // Thrift exceptions aren't very good in reporting, so we have to catch the exception here and // add details to it. log.debug("Unable to open transport to " + cassandraHost.getName()); //clientMonitor.incCounter(Counter.CONNECT_ERROR); throw new HectorTransportException("Unable to open transport to " + cassandraHost.getName() +" , " + e.getLocalizedMessage(), e); } } return this; } protected TTransport maybeWrapWithTFramedTransport(TTransport transport) { if (cassandraHost.getUseThriftFramedTransport()) { return new TFramedTransport(transport, cassandraHost.getMaxFrameSize()); } else { return transport; } } /** * {@inheritDoc} */ public boolean isOpen() { boolean open = false; if (transport != null) { open = transport.isOpen(); } if ( log.isTraceEnabled() ) { log.trace("Transport open status {} for client {}", open, this); } return open; } /** * If CassandraHost was not null we use {@link CassandraHost#getCassandraThriftSocketTimeout()} * if it was greater than zero. Otherwise look for an environment * variable name CASSANDRA_THRIFT_SOCKET_TIMEOUT value. * If doesn't exist, returns 0. * @param cassandraHost */ private int getTimeout(CassandraHost cassandraHost) { int timeoutVar = 0; if ( cassandraHost != null && cassandraHost.getCassandraThriftSocketTimeout() > 0 ) { timeoutVar = cassandraHost.getCassandraThriftSocketTimeout(); } else { String timeoutStr = System.getProperty( SystemProperties.CASSANDRA_THRIFT_SOCKET_TIMEOUT.toString()); if (timeoutStr != null && timeoutStr.length() > 0) { try { timeoutVar = Integer.parseInt(timeoutStr); } catch (NumberFormatException e) { log.error("Invalid value for CASSANDRA_THRIFT_SOCKET_TIMEOUT", e); } } } return timeoutVar; } /** * {@inheritDoc} */ public void startToUse() { useageStartTime = System.currentTimeMillis(); } /** * {@inheritDoc} */ public long getSinceLastUsed() { return System.currentTimeMillis() - useageStartTime; } @Override public String toString() { return String.format(NAME_FORMAT, cassandraHost.getUrl(), mySerial); } /** * Compares the toString of these clients */ @Override public boolean equals(Object obj) { return this.toString().equals(obj.toString()); } /** * {@inheritDoc} */ @Override public CassandraHost getCassandraHost() { return cassandraHost; } /** * {@inheritDoc} */ @Override public boolean isAlreadyAuthenticated(Map<String, String> credentials) { return credentials != null && this.credentials.equals(credentials); } /** * {@inheritDoc} */ @Override public void clearAuthentication() { credentials.clear(); } /** * {@inheritDoc} */ @Override public void setAuthenticated(Map<String, String> credentials) { clearAuthentication(); this.credentials.putAll(credentials); } /** * {@inheritDoc} */ public long getCreatedTime() { return createdTime; } /** * {@inheritDoc} */ @Override public long getLastSuccessTime() { return lastSuccessTime; } /** * {@inheritDoc} */ @Override public void updateLastSuccessTime() { lastSuccessTime = System.currentTimeMillis(); } }