package com.netflix.astyanax.cql;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.codahale.metrics.MetricRegistryListener;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Configuration;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.netflix.astyanax.AstyanaxConfiguration;
import com.netflix.astyanax.Keyspace;
import com.netflix.astyanax.KeyspaceTracerFactory;
import com.netflix.astyanax.connectionpool.ConnectionPoolConfiguration;
import com.netflix.astyanax.connectionpool.ConnectionPoolMonitor;
import com.netflix.astyanax.connectionpool.ConnectionPoolProxy.SeedHostListener;
import com.netflix.astyanax.connectionpool.Host;
import com.netflix.astyanax.connectionpool.OperationResult;
import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
import com.netflix.astyanax.cql.schema.CqlColumnDefinitionImpl;
import com.netflix.astyanax.cql.schema.CqlColumnFamilyDefinitionImpl;
import com.netflix.astyanax.cql.schema.CqlKeyspaceDefinitionImpl;
import com.netflix.astyanax.ddl.ColumnDefinition;
import com.netflix.astyanax.ddl.ColumnFamilyDefinition;
import com.netflix.astyanax.ddl.KeyspaceDefinition;
import com.netflix.astyanax.ddl.SchemaChangeResult;
/**
* Java Driver based impl of {@link Cluster} that implements ddl operations.
* The class encapsulates a java driver cluster and session object to provide all the functionality.
*
* Note that due to the way the object is setup via AstyanaxContext and CqlFamilyFactory, it needs to implements
* a {@link SeedHostListener} so that it can construct the cluster and session object appropriately once the seed hosts
* have been provided by the {@link HostSupplier} object
*
* @author poberai
*/
public class CqlClusterImpl implements com.netflix.astyanax.Cluster, SeedHostListener {
public volatile Cluster cluster;
private volatile Session session;
private final AstyanaxConfiguration astyanaxConfig;
private final KeyspaceTracerFactory tracerFactory;
private final Configuration javaDriverConfig;
private final ConnectionPoolMonitor cpMonitor;
private final MetricRegistryListener metricsRegListener;
public CqlClusterImpl(AstyanaxConfiguration asConfig, KeyspaceTracerFactory tracerFactory, ConnectionPoolConfiguration cpConfig, ConnectionPoolMonitor cpMonitor) {
this.astyanaxConfig = asConfig;
this.tracerFactory = tracerFactory;
this.javaDriverConfig = ((JavaDriverConnectionPoolConfigurationImpl)cpConfig).getJavaDriverConfig();
this.cpMonitor = cpMonitor;
this.metricsRegListener = ((JavaDriverConnectionPoolMonitorImpl)cpMonitor).getMetricsRegistryListener();
}
@Override
public String describeClusterName() throws ConnectionException {
return cluster.getMetadata().getClusterName();
}
@Override
public String getVersion() throws ConnectionException {
Statement query = QueryBuilder.select("release_version")
.from("system", "local")
.where(eq("key", "local"));
return session.execute(query).one().getString("release_version");
}
public void shutdown() {
cluster.close();
}
@Override
public String describeSnitch() throws ConnectionException {
throw new UnsupportedOperationException("Operation not supported");
}
@Override
public String describePartitioner() throws ConnectionException {
Statement query = QueryBuilder.select("partitioner")
.from("system", "local")
.where(eq("key", "local"));
return session.execute(query).one().getString("partitioner");
}
@Override
public Map<String, List<String>> describeSchemaVersions() throws ConnectionException {
return new CqlSchemaVersionReader(session).exec();
}
@Override
public KeyspaceDefinition makeKeyspaceDefinition() {
return new CqlKeyspaceDefinitionImpl(session);
}
@Override
public Properties getAllKeyspaceProperties() throws ConnectionException {
Properties properties = new Properties();
try {
List<KeyspaceDefinition> ksDefs = describeKeyspaces();
for(KeyspaceDefinition ksDef : ksDefs) {
Properties ksProps = ksDef.getProperties();
for (Object key : ksProps.keySet()) {
properties.put(ksDef.getName() + "." + key, ksProps.get(key));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return properties;
}
@Override
public Properties getKeyspaceProperties(String keyspace) throws ConnectionException {
try {
return describeKeyspace(keyspace.toLowerCase()).getProperties();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public List<KeyspaceDefinition> describeKeyspaces() throws ConnectionException {
Statement query = QueryBuilder.select().all().from("system", "schema_keyspaces");
List<KeyspaceDefinition> ksDefs = new ArrayList<KeyspaceDefinition>();
try {
for(Row row : session.execute(query).all()) {
String keyspaceName = row.getString("keyspace_name");
if (keyspaceName.equals("system") || keyspaceName.startsWith("system_")) {
continue;
}
ksDefs.add(new CqlKeyspaceDefinitionImpl(session, row));
}
return ksDefs;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public KeyspaceDefinition describeKeyspace(String ksName) throws ConnectionException {
return new CqlKeyspaceImpl(session, ksName, astyanaxConfig, tracerFactory,cpMonitor).describeKeyspace();
}
@Override
public Keyspace getKeyspace(String keyspace) throws ConnectionException {
return new CqlKeyspaceImpl(session, keyspace, astyanaxConfig, tracerFactory,cpMonitor);
}
@Override
public OperationResult<SchemaChangeResult> dropKeyspace(String keyspaceName) throws ConnectionException {
return new CqlKeyspaceImpl(session, keyspaceName.toLowerCase(), astyanaxConfig, tracerFactory,cpMonitor).dropKeyspace();
}
@Override
public OperationResult<SchemaChangeResult> addKeyspace(KeyspaceDefinition def) throws ConnectionException {
return ((CqlKeyspaceDefinitionImpl)def).execute();
}
@Override
public OperationResult<SchemaChangeResult> updateKeyspace(KeyspaceDefinition def) throws ConnectionException {
return ((CqlKeyspaceDefinitionImpl)def).alterKeyspace().execute();
}
@Override
public OperationResult<SchemaChangeResult> createKeyspace(Map<String, Object> options) throws ConnectionException {
String keyspaceName = (String) options.remove("name");
if (keyspaceName == null) {
throw new RuntimeException("Options missing 'name' property for keyspace name");
}
return new CqlKeyspaceDefinitionImpl(session, options).setName(keyspaceName).execute();
}
@Override
public OperationResult<SchemaChangeResult> createKeyspace(Properties props) throws ConnectionException {
String keyspaceName = (String) props.remove("name");
if (keyspaceName == null) {
throw new RuntimeException("Options missing 'name' property for keyspace name");
}
return new CqlKeyspaceDefinitionImpl(session, props).setName(keyspaceName).execute();
}
@Override
public OperationResult<SchemaChangeResult> updateKeyspace(Map<String, Object> options) throws ConnectionException {
String keyspaceName = (String) options.remove("name");
if (keyspaceName == null) {
throw new RuntimeException("Options missing 'name' property for keyspace name");
}
return new CqlKeyspaceDefinitionImpl(session, options).setName(keyspaceName).alterKeyspace().execute();
}
@Override
public OperationResult<SchemaChangeResult> updateKeyspace(Properties props) throws ConnectionException {
String keyspaceName = (String) props.remove("name");
if (keyspaceName == null) {
throw new RuntimeException("Options missing 'name' property for keyspace name");
}
return new CqlKeyspaceDefinitionImpl(session, props).setName(keyspaceName).alterKeyspace().execute();
}
@Override
public AstyanaxConfiguration getConfig() {
return astyanaxConfig;
}
@Override
public ColumnFamilyDefinition makeColumnFamilyDefinition() {
return new CqlColumnFamilyDefinitionImpl(session);
}
@Override
public ColumnDefinition makeColumnDefinition() {
return new CqlColumnDefinitionImpl();
}
@Override
public Properties getColumnFamilyProperties(String keyspace, String columnfamilyName) throws ConnectionException {
try {
return new CqlKeyspaceDefinitionImpl(session).setName(keyspace).getColumnFamily(columnfamilyName).getProperties();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public OperationResult<SchemaChangeResult> createColumnFamily(Map<String, Object> options) throws ConnectionException {
return new CqlColumnFamilyDefinitionImpl(session, null, options).execute();
}
@Override
public OperationResult<SchemaChangeResult> createColumnFamily(Properties props) throws ConnectionException {
return new CqlColumnFamilyDefinitionImpl(session, null, props).execute();
}
@Override
public OperationResult<SchemaChangeResult> updateColumnFamily(Map<String, Object> options) throws ConnectionException {
return new CqlColumnFamilyDefinitionImpl(session, null, options).alterTable().execute();
}
@Override
public OperationResult<SchemaChangeResult> updateColumnFamily(Properties props) throws ConnectionException {
return new CqlColumnFamilyDefinitionImpl(session, null, props).alterTable().execute();
}
@Override
public OperationResult<SchemaChangeResult> dropColumnFamily(String keyspaceName, String columnFamilyName) throws ConnectionException {
return new CqlKeyspaceImpl(session, keyspaceName, astyanaxConfig, tracerFactory,cpMonitor).dropColumnFamily(columnFamilyName);
}
@Override
public OperationResult<SchemaChangeResult> addColumnFamily(ColumnFamilyDefinition def) throws ConnectionException {
return ((CqlColumnFamilyDefinitionImpl)def).execute();
}
@Override
public OperationResult<SchemaChangeResult> updateColumnFamily(ColumnFamilyDefinition def) throws ConnectionException {
return ((CqlColumnFamilyDefinitionImpl)def).alterTable().execute();
}
@Override
public void setHosts(Collection<Host> hosts, int port) {
List<Host> hostList = Lists.newArrayList(hosts);
List<String> contactPoints = Lists.transform(hostList, new Function<Host, String>() {
@Override
public String apply(Host input) {
if (input != null) {
return input.getHostName();
}
return null;
}
});
Configuration config = javaDriverConfig;
// We really need a mechanism to easily override Configuration on the builder
Cluster.Builder builder = Cluster.builder()
.addContactPoints(contactPoints.toArray(new String[0]))
.withPort(port)
.withLoadBalancingPolicy(config.getPolicies().getLoadBalancingPolicy())
.withReconnectionPolicy(config.getPolicies().getReconnectionPolicy())
.withRetryPolicy(config.getPolicies().getRetryPolicy())
.withCompression(config.getProtocolOptions().getCompression())
.withPoolingOptions(config.getPoolingOptions())
.withSocketOptions(config.getSocketOptions())
.withQueryOptions(config.getQueryOptions());
if (config.getMetricsOptions() == null) {
builder.withoutMetrics();
} else if (!config.getMetricsOptions().isJMXReportingEnabled()) {
builder.withoutJMXReporting();
}
this.cluster = builder.build();
if (!(this.cpMonitor instanceof JavaDriverConnectionPoolMonitorImpl))
this.cluster.getMetrics().getRegistry().addListener((MetricRegistryListener) this.metricsRegListener);
this.session = cluster.connect();
}
}