package ch.uzh.ifi.ddis.ifp.esper.cassandra;
/*
* #%L
* Cassandra for Esper
* %%
* Copyright (C) 2013 University of Zurich
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
import java.util.Properties;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Cluster.Builder;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.AlreadyExistsException;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import com.espertech.esper.client.hook.VirtualDataWindowContext;
import com.espertech.esper.client.hook.VirtualDataWindowFactory;
import com.espertech.esper.client.hook.VirtualDataWindowFactoryContext;
/**
* <p>
* Factory for creating {@link CassandraVirtualDataWindow} objects.
* </p>
*
* @author Thomas Scharrenbach
* @version 0.2.3
* @since 0.0.1
*
*/
public class CassandraVirtualDataWindowFactory implements
VirtualDataWindowFactory {
private static final Logger _log = LoggerFactory
.getLogger(CassandraVirtualDataWindowFactory.class);
//
//
//
/**
* Key for the system {@link Properties}, which stores the username for
* authenticating to Cassandra. Can be overridden by the configuration.
*/
public static final String CASSANDRA_USERNAME = "cassandra-username";
/**
* Key for the system {@link Properties}, which stores the root username for
* authenticating to Cassandra. Can be overridden by the configuration.
*/
public static final String CASSANDRA_ROOT_USERNAME = "cassandra-root-username";
/**
* Key for the system {@link Properties}, which stores the password for
* authenticating to Cassandra. Can be overridden by the configuration.
*/
public static final String CASSANDRA_PASSWORD = "cassandra-password";
/**
* Key for the system {@link Properties}, which stores the password for
* authenticating to Cassandra. Can be overridden by the configuration.
*/
public static final String CASSANDRA_ROOT_PASSWORD = "cassandra-root-password";
//
//
//
/**
* Connection for SELECT, INSERT, and UPDATE.
*/
private Cluster _cluster;
/**
* Connection for creating the keyspace, the table and granting access
* rights.
*/
private Cluster _rootCluster;
private CassandraConfiguration _configuration;
/**
* Stores the user which performs SELECT, INSERT, and UPDATE queries.
*/
private String _username;
//
//
//
public CassandraVirtualDataWindowFactory() {
return;
}
//
//
//
private Cluster connectToCluster(String node, String username,
String password) throws Exception {
Builder clusterBuilder = Cluster.builder().withoutMetrics();
Cluster cluster = null;
try {
_log.info("Started connecting to cluster: {} ...", node);
_log.debug("Adding contact point...");
clusterBuilder = clusterBuilder.addContactPoint(node);
if (username != null && password != null) {
_log.debug("Adding credentials...");
clusterBuilder = clusterBuilder.withCredentials(username,
password);
}
_log.debug("Building cluster user...");
cluster = clusterBuilder.build();
_log.info("Finished connecting to cluster user: {} .", node);
} catch (Exception e) {
_log.error("Error connecting to cluster user: {} !", node);
final String errorMessage = String.format(
"Could not connect to Cassandra cluster!", node);
throw new Exception(errorMessage, e);
}
return cluster;
}
private void createKeyspace(String node, String keyspace) throws Exception {
Session session = null;
Exception error = null;
try {
_log.info("Started checking whether keyspace {} exists...",
keyspace);
session = _rootCluster.connect(keyspace);
_log.info("Finished checking whether keyspace {} exists...",
keyspace);
_log.info("Keyspace {} exists, nothing to do.", keyspace);
}
// Keyspace does not exist, so session is not null...
catch (AlreadyExistsException ae) {
}
// Keyspace does not exist, so we try to create it.
// In that case, session is null
catch (InvalidQueryException ae) {
}
// Unknown exception -> error
catch (Exception e) {
_log.error("Unexcpected Exception when checking for keyspace {}!",
keyspace, e);
error = e;
}
if (session == null && error == null) {
_log.info("Finished checking whether keyspace {} exists...",
keyspace);
_log.info("Keyspace does not yet {} exist, trying to create it",
keyspace);
try {
_log.info("Started creating keyspace {} ...", keyspace);
session = _rootCluster.connect();
final String query = String.format("" + "CREATE keyspace %s "
+ "WITH replication = "
+ "{'class': 'SimpleStrategy', "
+ "'replication_factor' : 3}", keyspace);
session.execute(query);
_log.info("Finished creating keyspace {} .", keyspace);
} catch (Exception e) {
_log.error("Error creating keyspace {}", keyspace, e);
error = e;
}
// In any case, shut down the session for the root user.
finally {
if (session != null) {
_log.info("Started shutting down session for creating keyspace ...");
session.shutdown();
_log.info("Finished shutting down session for creating keyspace.");
}
}
}
if (error != null) {
throw error;
}
}
private void createTable(VirtualDataWindowContext context) throws Exception {
Session session = null;
Exception error = null;
final String table = context.getNamedWindowName();
final String keyspace = context.getViewFactoryContext()
.getNamespaceName();
boolean tableExists = true;
try {
_log.info("Started checking whether table {} exists...", table);
session = _rootCluster.connect(keyspace);
session.execute(String.format("SELECT * FROM %s LIMIT 10", table));
_log.info("Finished checking whether table {} exists...", table);
_log.info("Table {} exists, nothing to do.", table);
}
// Keyspace does not exist, so we try to create it.
catch (InvalidQueryException ae) {
tableExists = false;
}
// Unknown exception -> error
catch (Exception e) {
tableExists = false;
_log.error("Unexcpected Exception when checking for table {}!",
table, e);
error = e;
}
if (error == null) {
_log.info("Finished checking whether table {} exists...", table);
try {
session = _rootCluster.connect(keyspace);
if (!tableExists) {
_log.info(
"Table does not yet {} exist, trying to create it",
table);
try {
_log.info("Started creating table {} ...", table);
final String createQuery = QueryStringBuilder
.createCreateTableQuery(context);
_log.debug("Executing create query: {}", createQuery);
session.execute(createQuery);
_log.info("Finished creating table {} .", table);
} catch (Exception e) {
_log.error("Error creating table {} .", table);
error = e;
}
}
if (_username != null && error == null) {
try {
_log.info("Started granting rights to table {} ...",
table);
final String grantQuery = String.format(
"GRANT ALL on %s TO %s", table, _username);
_log.debug("Executing grant query: {}", grantQuery);
session.execute(grantQuery);
_log.info("Finished granting rights to table {}.",
table);
} catch (Exception e) {
_log.error("Error granting rights to table {}.", table,
e);
error = e;
}
}
} catch (Exception e) {
_log.error("Error creating table {}", table, e);
error = e;
}
// In any case, shut down the session for the root user.
finally {
if (session != null) {
_log.info("Started shutting down session for creating table ...");
session.shutdown();
_log.info("Finished shutting down session for creating table.");
}
}
}
if (error != null) {
throw error;
}
}
//
//
//
/**
* <p>
* Initializes the Cassandra {@link Cluster}.
* </p>
*
* @throws java.lang.RuntimeException
* In case the connection to Cassandra fails.
*/
@Override
public void initialize(VirtualDataWindowFactoryContext factoryContext) {
final String keyspace = factoryContext.getViewFactoryContext()
.getNamespaceName();
final Object[] windowParameters = factoryContext.getParameters();
if (windowParameters.length < 1) {
throw new RuntimeException(String.format(
"Cassandra window has at least one parameter. "
+ "Found %s", windowParameters.length));
}
final String parameter = windowParameters[0].toString().trim();
_log.info("Started parsing Cassandra configuration...");
try {
_configuration = CassandraConfiguration.create(parameter);
} catch (Exception pe) {
_log.error("Error parsing Cassandra configuration!");
throw new RuntimeException(pe);
}
_log.info("Finished parsing Cassandra configuration.");
final String node = _configuration.getHost();
_username = System.getProperty(CASSANDRA_USERNAME, null);
final String password = System.getProperty(CASSANDRA_PASSWORD, null);
final String rootUsername = System.getProperty(CASSANDRA_ROOT_USERNAME,
null);
final String rootPassword = System.getProperty(CASSANDRA_ROOT_PASSWORD,
null);
try {
_rootCluster = connectToCluster(node, rootUsername, rootPassword);
createKeyspace(node, keyspace);
_cluster = connectToCluster(node, _username, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* <p>
* Creates a new {@link CassandraVirtualDataWindow} by creating a Cassandra
* {@link Session}.
* </p>
* <p>
* <strong>Note: This method is thread-safe.</strong>
* </p>
*
* @throws RuntimeException
* In case the cluster is null or the creation of the session
* fails.
*/
@Override
public CassandraVirtualDataWindow create(VirtualDataWindowContext context) {
if (_cluster == null) {
throw new RuntimeException(
"Cassandra cluster has not yet been initialized!");
}
CassandraVirtualDataWindow result = null;
synchronized (_cluster) {
try {
_log.info("Started connecting to cluster...");
final Session session = _cluster.connect(_configuration
.getKeyspace());
_log.info("Finished connecting to cluster...");
createTable(context);
result = new CassandraVirtualDataWindow(session,
context.getNamedWindowName(), context);
} catch (Exception e) {
_log.error("Error connecting to cluster!");
throw new RuntimeException(e);
}
}
return result;
}
/**
* <p>
* Shuts down the connection to the Cassandra {@link Cluster}.
* </p>
*/
@Override
public void destroyAllContextPartitions() {
close();
}
/**
* @return null.
*/
@Override
public Set<String> getUniqueKeyPropertyNames() {
return null;
}
//
//
//
/**
* <p>
* Disconnect from Cassandra {@link Cluster}.
* </p>
*/
protected void close() {
try {
_log.info("Started shutting down connection to Cassandra cluster...");
if (_cluster != null) {
_cluster.shutdown();
}
_log.info("Finished shutting down connection to Cassandra cluster.");
}
//
catch (Exception e) {
_log.error("Error shutting down connection to Cassandra cluster!",
e);
}
try {
_log.info("Started shutting down connection to Cassandra root cluster...");
if (_cluster != null) {
_rootCluster.shutdown();
}
_log.info("Finished shutting down connection to Cassandra root cluster.");
}
//
catch (Exception e) {
_log.error(
"Error shutting down connection to Cassandra root cluster!",
e);
}
}
}