/*
* Copyright (C) 2012-2016 DuyHai DOAN
*
* 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 info.archinnov.achilles.embedded;
import static info.archinnov.achilles.embedded.CassandraEmbeddedConfigParameters.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
import info.archinnov.achilles.type.TypedMap;
import info.archinnov.achilles.validation.Validator;
/**
* Builder for embedded Cassandra server
* <pre class="code"><code class="java">
* CassandraEmbeddedServerBuilder
* .builder()
* .withListenAdress("127.0.0.1")
* .withRpcAdress("127.0.0.1")
* .withBroadcastAdress("127.0.0.1")
* .withBroadcastRPCAdress("127.0.0.1")
* .withClusterName("Test Cluster")
* .withDataFolder("/home/user/cassandra/data")
* .withCommitLogFolder("/home/user/cassandra/commitlog")
* .withSavedCachesFolder("/home/user/cassandra/saved_caches")
* .cleanDataFilesAtStartup(true)
* .withClusterName("Test Cluster")
* .withKeyspaceName("achilles_test")
* .withCQLPort(9042)
* .withThriftPort(9160)
* .withStoragePort(7990)
* .withStorageSSLPort(7999)
* .withDurableWrite(true)
* .withScript("init_script.cql")
* .buildNativeCluster();
* </code></pre>
*/
public class CassandraEmbeddedServerBuilder {
private String listenAddress;
private String rpcAddress;
private String broadcastAddress = "";
private String broadcastRpcAddress = "";
private CassandraShutDownHook cassandraShutDownHook;
private String dataFileFolder;
private String commitLogFolder;
private String savedCachesFolder;
private String hintsFolder;
private boolean cleanDataFiles = true;
private boolean cleanConfigFile = true;
private int concurrentReads;
private int concurrentWrites;
private int cqlPort;
private int thriftPort;
private int storagePort;
private int storageSSLPort;
private String clusterName;
private String keyspaceName;
private boolean durableWrite = false;
private boolean useUnsafeCassandraDaemon = false;
private List<String> scriptLocations = new ArrayList<>();
private Map<String, Map<String, Object>> scriptTemplates = new HashMap<>();
private TypedMap cassandraParams = new TypedMap();
private CassandraEmbeddedServerBuilder() {
}
/**
* Create a new CassandraEmbeddedServerBuilder
*
* @return CassandraEmbeddedServerBuilder
*/
public static CassandraEmbeddedServerBuilder builder() {
return new CassandraEmbeddedServerBuilder();
}
/**
* Specify the listen address. Default value = <strong>localhost</strong>
*
* @param listenAddress the listen address
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withListenAddress(String listenAddress) {
this.listenAddress = listenAddress;
return this;
}
/**
* Specify the rpc address. Default value = <strong>localhost</strong>
*
* @param rpcAddress the RPC address
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withRpcAddress(String rpcAddress) {
this.rpcAddress = rpcAddress;
return this;
}
/**
* Specify the broadcast address. Default value = <strong>localhost</strong>
* <br/>
* <br/>
* Leaving this blank will set it to the same value as <strong>listen_address</strong>
* @param broadcastAddress the address to broadcast to other Cassandra nodes.
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withBroadcastAddress(String broadcastAddress) {
this.broadcastAddress = broadcastAddress;
return this;
}
/**
* Specify the broadcast RPC address. Default value = <strong>localhost</strong>
* <br/>
* <br/>
* This cannot be set to <strong>0.0.0.0</strong>. If left blank, this will be set to the value of
* <strong>rpc_address</strong>.
* If <strong>rpc_address</strong> is set to <strong>0.0.0.0</strong>,
* <strong>broadcast_rpc_address</strong> must be set
* @param broadcastRpcAddress the RPC address to broadcast to drivers and other Cassandra nodes
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withBroadcastRpcAddress(String broadcastRpcAddress) {
this.broadcastRpcAddress = broadcastRpcAddress;
return this;
}
/**
* Inject a shutdown hook to control when to shutdown the embedded Cassandra process.
* <br/>
* <pre class="code"><code class="java">
*
* CassandraShutDownHook shutdownHook = new CassandraShutDownHook();
*
* Session session = CassandraEmbeddedServerBuilder.builder()
* .withShutdownHook(shutdownHook)
* ...
* .buildNativeSession();
*
* ...
*
* shutdownHook.shutdownNow();
* </code></pre>
* <br/>
* <strong>Please note that upon call on <em>shutdownNow()</em>, Achilles will trigger the shutdown of:</strong>
* <ul>
* <li><strong>the embedded Cassandra server</strong></li>
* <li><strong>the associated Cluster object</strong></li>
* <li><strong>the associated Session object</strong></li>
* </ul>
* @param cassandraShutDownHook shutdown hook to control the shutdown of the embedded Cassandra process;
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withShutdownHook(CassandraShutDownHook cassandraShutDownHook) {
this.cassandraShutDownHook = cassandraShutDownHook;
return this;
}
/**
* Specify data folder for the embedded Cassandra server. Default value is
* 'target/cassandra_embedded/data'
*
* @param dataFolder data folder for the embedded Cassandra server
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withDataFolder(String dataFolder) {
this.dataFileFolder = dataFolder;
return this;
}
/**
* Specify commit log folder for the embedded Cassandra server. Default
* value is 'target/cassandra_embedded/commitlog'
*
* @param commitLogFolder commit log folder for the embedded Cassandra server
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withCommitLogFolder(String commitLogFolder) {
this.commitLogFolder = commitLogFolder;
return this;
}
/**
* Specify saved caches folder for the embedded Cassandra server. Default
* value is 'target/cassandra_embedded/saved_caches'
*
* @param savedCachesFolder saved caches folder for the embedded Cassandra server
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withSavedCachesFolder(String savedCachesFolder) {
this.savedCachesFolder = savedCachesFolder;
return this;
}
/**
* Specify hints folder for the embedded Cassandra server. Default
* value is 'target/cassandra_embedded/hints'
*
* @param hintsFolder hints folder for the embedded Cassandra server
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withHintsFolder(String hintsFolder) {
this.hintsFolder = hintsFolder;
return this;
}
/**
* Whether to clean all data files in data folder, commit log folder and
* saved caches folder at startup or not. Default value = 'true'
*
* @param cleanDataFilesAtStartup whether to clean all data files at startup or not
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder cleanDataFilesAtStartup(boolean cleanDataFilesAtStartup) {
this.cleanDataFiles = cleanDataFilesAtStartup;
return this;
}
/**
* Specify the cluster name for the embedded Cassandra server. Default value
* is 'Achilles Embedded Cassandra Cluster'
*
* @param clusterName cluster name
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withClusterName(String clusterName) {
this.clusterName = clusterName;
return this;
}
/**
* Specify the keyspace name for the embedded Cassandra server. Default
* value is 'achilles_embedded'
* <br/>
* <strong>If the keyspace does not exist, it will be created by Achilles</strong>
*
* @param keyspaceName keyspace name
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withKeyspaceName(String keyspaceName) {
this.keyspaceName = keyspaceName;
return this;
}
/**
* Specify the native transport port (CQL port) for the embedded Cassandra
* server. If not set, the port will be randomized at runtime
*
* @param clqPort native transport port
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withCQLPort(int clqPort) {
this.cqlPort = clqPort;
return this;
}
/**
* Specify the rpc port (Thrift port) for the embedded Cassandra server. If
* not set, the port will be randomized at runtime
*
* @param thriftPort rpc port
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withThriftPort(int thriftPort) {
this.thriftPort = thriftPort;
return this;
}
/**
* Specify the storage port for the embedded Cassandra server. If not set,
* the port will be randomized at runtime
*
* @param storagePort storage port
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withStoragePort(int storagePort) {
this.storagePort = storagePort;
return this;
}
/**
* Specify the storage SSL port for the embedded Cassandra server. If not
* set, the port will be randomized at runtime
*
* @param storageSSLPort storage SSL port
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withStorageSSLPort(int storageSSLPort) {
this.storageSSLPort = storageSSLPort;
return this;
}
/**
* Specify the number threads for concurrent reads for the embedded Cassandra
* server. If not set, 32
*
* @param concurrentReads the number threads for concurrent reads
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withConcurrentReads(int concurrentReads) {
this.concurrentReads = concurrentReads;
return this;
}
/**
* Specify the number threads for concurrent writes for the embedded Cassandra
* server. If not set, 32
*
* @param concurrentWrites the number threads for concurrent writes
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withConcurrentWrites(int concurrentWrites) {
this.concurrentWrites = concurrentWrites;
return this;
}
/**
* Specify the 'durable write' property for the embedded Cassandra server.
* Default value is 'false'. If not set, Cassandra will not write to commit
* log.
* For testing purpose, it is recommended to deactivate it to speed up tests
*
* @param durableWrite whether to activate 'durable write' or not
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withDurableWrite(boolean durableWrite) {
this.durableWrite = durableWrite;
return this;
}
/**
* Load an CQL script in the class path and execute it upon initialization
* of the embedded Cassandra server
* <br/>
* Call this method as many times as there are CQL scripts to be executed.
* <br/>
* Example:
* <br/>
* <pre class="code"><code class="java">
* CassandraEmbeddedServerBuilder
* .withScript("script1.cql")
* .withScript("script2.cql")
* ...
* .build();
* </code></pre>
*
* @param scriptLocation location of the CQL script in the <strong>class path</strong>
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withScript(String scriptLocation) {
Validator.validateNotBlank(scriptLocation, "The script location should not be blank while executing CassandraEmbeddedServerBuilder.withScript()");
scriptLocations.add(scriptLocation.trim());
return this;
}
/**
* Load an CQL script template in the class path, inject the values into the template
* to produce the final script and execute it upon initialization
* of the embedded Cassandra server
* <br/>
* Call this method as many times as there are CQL templates to be executed.
* <br/>
* Example:
* <br/>
* <pre class="code"><code class="java">
* Map<String, Object> map1 = new HashMap<>();
* map1.put("id", 100L);
* map1.put("date", new Date());
* ...
* CassandraEmbeddedServerBuilder
* .withScriptTemplate("script1.cql", map1)
* .withScriptTemplate("script2.cql", map2)
* ...
* .build();
* </code></pre>
*
* @param scriptTemplateLocation location of the CQL script in the <strong>class path</strong>
* @param values values to inject into the template.
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withScriptTemplate(String scriptTemplateLocation, Map<String, Object> values) {
Validator.validateNotBlank(scriptTemplateLocation, "The script template should not be blank while executing CassandraEmbeddedServerBuilder.withScriptTemplate()");
Validator.validateNotEmpty(values, "The template values should not be empty while executing CassandraEmbeddedServerBuilder.withScriptTemplate()");
scriptTemplates.put(scriptTemplateLocation.trim(), values);
return this;
}
/**
* Inject Cassandra parameters
*
* @param cassandraParams cassandra parameter
* @return CassandraEmbeddedServerBuilder
*/
public CassandraEmbeddedServerBuilder withParams(TypedMap cassandraParams) {
this.cassandraParams.putAll(cassandraParams);
return this;
}
/**
* Use an unsafe version of the Cassandra daemon. This version will:
* <ul>
* <li>disable JMX</li>
* <li>disable legacy schema migration</li>
* <li>disable pre-3.0 hints migration</li>
* <li>disable pre-3.0 batch entries migration</li>
* <li>disable auto compaction on all keyspaces. <strong>Your test/dev data should fit in memory normally</strong></li>
* <li>disable metrics</li>
* <li>disable GCInspector</li>
* <li>disable native mlock system call</li>
* <li>disable Thrift server</li>
* <li>disable startup checks (Jemalloc, validLaunchDate, JMXPorts, JvmOptions, JnaInitialization, initSigarLibrary, dataDirs, SSTablesFormat, SystemKeyspaceState, Datacenter, Rack)</li>
* <li>disable materialized view rebuild. <strong>You should clean your data folder between each test anyway</strong></li>
* <li>disable the SizeEstimatesRecorder (estimate SSTable size, who cares for unit testing or dev ?)</li>
* </ul>
* @return
*/
public CassandraEmbeddedServerBuilder useUnsafeCassandraDeamon() {
this.useUnsafeCassandraDaemon = true;
return this;
}
/**
* Use an unsafe version of the Cassandra daemon. This version will:
* <ul>
* <li>disable JMX</li>
* <li>disable legacy schema migration</li>
* <li>disable pre-3.0 hints migration</li>
* <li>disable pre-3.0 batch entries migration</li>
* <li>disable auto compaction on all keyspaces. <strong>Your test/dev data should fit in memory normally</strong></li>
* <li>disable metrics</li>
* <li>disable GCInspector</li>
* <li>disable native mlock system call</li>
* <li>disable Thrift server</li>
* <li>disable startup checks (Jemalloc, validLaunchDate, JMXPorts, JvmOptions, JnaInitialization, initSigarLibrary, dataDirs, SSTablesFormat, SystemKeyspaceState, Datacenter, Rack)</li>
* <li>disable materialized view rebuild. <strong>You should clean your data folder between each test anyway</strong></li>
* <li>disable the SizeEstimatesRecorder (estimate SSTable size, who cares for unit testing or dev ?)</li>
* </ul>
* @return
*/
public CassandraEmbeddedServerBuilder useUnsafeCassandraDeamon(boolean useUnsafeCassandraDaemon) {
this.useUnsafeCassandraDaemon = useUnsafeCassandraDaemon;
return this;
}
/**
* Start an embedded Cassandra server but DO NOT bootstrap Achilles
*
* @return native Java driver core Cluster
*/
public Cluster buildNativeCluster() {
final CassandraEmbeddedServer embeddedServer = new CassandraEmbeddedServer(buildConfigMap());
return embeddedServer.getNativeCluster();
}
/**
* Start an embedded Cassandra server but DO NOT bootstrap Achilles
*
* @return native Java driver core Session
*/
public Session buildNativeSession() {
final CassandraEmbeddedServer embeddedServer = new CassandraEmbeddedServer(buildConfigMap());
return embeddedServer.getNativeSession();
}
public CassandraEmbeddedServer buildServer() {
return new CassandraEmbeddedServer(buildConfigMap());
}
private TypedMap buildConfigMap() {
cassandraParams.put(CLEAN_CASSANDRA_DATA_FILES, cleanDataFiles);
cassandraParams.put(CLEAN_CASSANDRA_CONFIG_FILE, cleanConfigFile);
if (isNotBlank(listenAddress))
cassandraParams.put(LISTEN_ADDRESS, listenAddress);
if (isNotBlank(rpcAddress))
cassandraParams.put(RPC_ADDRESS, rpcAddress);
if (broadcastAddress != null)
cassandraParams.put(BROADCAST_ADDRESS, broadcastAddress);
if (broadcastRpcAddress != null)
cassandraParams.put(BROADCAST_RPC_ADDRESS, broadcastRpcAddress);
if (cassandraShutDownHook != null)
cassandraParams.put(SHUTDOWN_HOOK, cassandraShutDownHook);
if (isNotBlank(dataFileFolder))
cassandraParams.put(DATA_FILE_FOLDER, dataFileFolder);
if (isNotBlank(commitLogFolder))
cassandraParams.put(COMMIT_LOG_FOLDER, commitLogFolder);
if (isNotBlank(savedCachesFolder))
cassandraParams.put(SAVED_CACHES_FOLDER, savedCachesFolder);
if (isNotBlank(hintsFolder))
cassandraParams.put(HINTS_FOLDER, hintsFolder);
if (isNotBlank(clusterName))
cassandraParams.put(CLUSTER_NAME, clusterName);
if (isNotBlank(keyspaceName))
cassandraParams.put(DEFAULT_KEYSPACE_NAME, keyspaceName);
if (cqlPort > 0)
cassandraParams.put(CASSANDRA_CQL_PORT, cqlPort);
if (thriftPort > 0)
cassandraParams.put(CASSANDRA_THRIFT_PORT, thriftPort);
if (storagePort > 0)
cassandraParams.put(CASSANDRA_STORAGE_PORT, storagePort);
if (storageSSLPort > 0)
cassandraParams.put(CASSANDRA_STORAGE_SSL_PORT, storageSSLPort);
if (concurrentReads > 0)
cassandraParams.put(CASSANDRA_CONCURRENT_READS, concurrentReads);
if (concurrentWrites > 0)
cassandraParams.put(CASSANDRA_CONCURRENT_READS, concurrentWrites);
if (scriptLocations.size() > 0) {
final List<String> existingScriptLocations = cassandraParams.getTypedOr(SCRIPT_LOCATIONS, new ArrayList<>());
existingScriptLocations.addAll(scriptLocations);
cassandraParams.put(SCRIPT_LOCATIONS, existingScriptLocations);
}
if (scriptTemplates.size() > 0) {
final Map<String, Map<String, Object>> existingScriptTemplates = cassandraParams.getTypedOr(SCRIPT_TEMPLATES, new HashMap<>());
existingScriptTemplates.putAll(scriptTemplates);
cassandraParams.put(SCRIPT_TEMPLATES, existingScriptTemplates);
}
if (useUnsafeCassandraDaemon) {
cassandraParams.put(USE_UNSAFE_CASSANDRA_DAEMON, true);
}
cassandraParams.put(KEYSPACE_DURABLE_WRITE, durableWrite);
TypedMap parameters = CassandraEmbeddedConfigParameters.mergeWithDefaultParameters(cassandraParams);
return parameters;
}
}