Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.nosan.embedded.cassandra.DefaultCassandra Maven / Gradle / Ivy
/*
* Copyright 2020-2021 the original author or authors.
*
* 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
*
* https://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.github.nosan.embedded.cassandra;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.github.nosan.embedded.cassandra.commons.logging.Logger;
class DefaultCassandra implements Cassandra {
private static final Logger LOGGER = Logger.get(DefaultCassandra.class);
private final String name;
private final Version version;
private final boolean registerShutdownHook;
private final Path workingDirectory;
private final Set workingDirectoryCustomizers;
private final WorkingDirectoryInitializer workingDirectoryInitializer;
private final WorkingDirectoryDestroyer workingDirectoryDestroyer;
private final Duration startupTimeout;
private final Object lock = new Object();
private final CassandraDatabaseFactory databaseFactory;
private final Logger logger;
private volatile boolean started = false;
private volatile boolean running = false;
private volatile Thread shutdownHookThread;
private volatile CassandraDatabase database;
private volatile Settings settings;
DefaultCassandra(String name, Version version, Path workingDirectory, boolean registerShutdownHook,
WorkingDirectoryInitializer workingDirectoryInitializer,
WorkingDirectoryDestroyer workingDirectoryDestroyer, Duration startupTimeout,
Set workingDirectoryCustomizers,
CassandraDatabaseFactory databaseFactory, Logger logger) {
this.name = name;
this.version = version;
this.startupTimeout = startupTimeout;
this.workingDirectoryInitializer = workingDirectoryInitializer;
this.registerShutdownHook = registerShutdownHook;
this.workingDirectory = workingDirectory;
this.workingDirectoryDestroyer = workingDirectoryDestroyer;
this.databaseFactory = databaseFactory;
this.workingDirectoryCustomizers = Collections.unmodifiableSet(workingDirectoryCustomizers);
this.logger = logger;
}
@Override
public synchronized void start() throws CassandraException {
if (this.started) {
return;
}
this.settings = null;
this.running = false;
this.database = null;
init();
doStart();
await();
//if a database was stopped outside this class.
this.database.onExit().thenRun(this::doStop);
}
@Override
public synchronized void stop() throws CassandraException {
if (!this.started) {
return;
}
doStop();
}
@Override
public synchronized Settings getSettings() {
Settings settings = this.settings;
if (settings == null) {
throw new IllegalStateException("The getSettings() method was called but start() had not been called");
}
return settings;
}
@Override
public boolean isRunning() {
CassandraDatabase database = this.database;
return this.running && (database != null && database.isAlive());
}
@Override
public String getName() {
return this.name;
}
@Override
public Version getVersion() {
return this.version;
}
@Override
public Path getWorkingDirectory() {
return this.workingDirectory;
}
@Override
public String toString() {
return "DefaultCassandra{" + "name='" + this.name + "', version='" + this.version + "'}";
}
private void init() {
Version version = this.version;
try {
Files.createDirectories(this.workingDirectory);
this.workingDirectoryInitializer.init(this.workingDirectory, version);
for (WorkingDirectoryCustomizer workingDirectoryCustomizer : this.workingDirectoryCustomizers) {
workingDirectoryCustomizer.customize(this.workingDirectory, version);
}
this.database = this.databaseFactory.create(this.workingDirectory);
}
catch (Exception ex) {
destroyWorkingDirectory();
throw new CassandraException(
String.format("Unable to initialize %s. Caused by: %s", this, ex), ex);
}
}
private void doStart() {
synchronized (this.lock) {
try {
this.started = true;
addShutdownHook();
this.database.start();
}
catch (Exception ex) {
try {
doStop();
}
catch (Exception suppressed) {
ex.addSuppressed(suppressed);
}
throw new CassandraException(
String.format("Unable to start %s. Caused by: %s", this, ex), ex);
}
}
}
private void doStop() {
synchronized (this.lock) {
if (!this.started) {
return;
}
CassandraDatabase database = this.database;
if (database != null) {
try {
database.stop();
}
catch (Exception ex) {
throw new CassandraException(
String.format("Unable to stop %s. Caused by: %s", this, ex), ex);
}
}
destroyWorkingDirectory();
removeShutdownHook();
this.started = false;
this.running = false;
this.database = null;
}
}
private void await() {
CassandraDatabase database = this.database;
Duration timeout = this.startupTimeout;
database.getStdOut().attach(this.logger::info);
database.getStdErr().attach(this.logger::error);
try (NativeTransportParser nativeTransport = new NativeTransportParser(database);
RpcTransportParser rpcTransport = new RpcTransportParser(database);
OutputCollector outputCollector = new OutputCollector(database);
ErrorCollector errorCollector = new ErrorCollector(database);
StartupParser startup = new StartupParser(database)) {
long start = System.nanoTime();
long rem = timeout.toNanos();
while (rem > 0 && database.isAlive()
&& !(nativeTransport.isParsed() && rpcTransport.isParsed() && startup.isComplete())) {
Thread.sleep(Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
rem = timeout.toNanos() - (System.nanoTime() - start);
}
while (rem > 0 && database.isAlive() && !(connect(nativeTransport) && connect(rpcTransport))) {
Thread.sleep(Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
rem = timeout.toNanos() - (System.nanoTime() - start);
}
if (!database.isAlive() || nativeTransport.isFailed() || rpcTransport.isFailed()) {
StringBuilder message = new StringBuilder(String.format("'%s' is not alive.", database))
.append(" Please see logs for more details.");
List errors = errorCollector.getErrors();
if (!errors.isEmpty()) {
message.append(String.format("%nErrors:%n%s", String.join(System.lineSeparator(), errors)));
}
message.append(String.format("%nOutput:%n%s",
String.join(System.lineSeparator(), outputCollector.getOutput())));
throw new IOException(message.toString());
}
if (rem <= 0) {
throw new IllegalStateException(String.format("%s couldn't be started within %sms",
database, this.startupTimeout.toMillis()));
}
InetAddress address = Optional.ofNullable(nativeTransport.getAddress())
.orElseGet(rpcTransport::getAddress);
this.settings = new DefaultSettings(database.getName(), database.getVersion(), address,
nativeTransport.isStarted(), nativeTransport.getPort(), nativeTransport.getSslPort(),
rpcTransport.isStarted(), rpcTransport.getPort(), database.getConfigurationFile(),
database.getWorkingDirectory(), database.getJvmOptions(), database.getSystemProperties(),
database.getEnvironmentVariables(), database.getConfigProperties());
this.running = true;
}
catch (Exception ex) {
try {
doStop();
}
catch (Exception suppressed) {
ex.addSuppressed(suppressed);
}
if (ex instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
throw new CassandraException(
String.format("Unable to await %s. Caused by: %s", this, ex), ex);
}
}
private void addShutdownHook() {
if (this.registerShutdownHook && this.shutdownHookThread == null) {
Thread thread = new Thread(this::doStop, this.name + "-sh");
Runtime.getRuntime().addShutdownHook(thread);
this.shutdownHookThread = thread;
}
}
private void removeShutdownHook() {
Thread shutdownHookThread = this.shutdownHookThread;
if (shutdownHookThread != null && shutdownHookThread != Thread.currentThread()) {
try {
Runtime.getRuntime().removeShutdownHook(shutdownHookThread);
this.shutdownHookThread = null;
}
catch (Exception ex) {
// If the virtual machine is already in the process of shutting down
}
}
}
private void destroyWorkingDirectory() {
try {
this.workingDirectoryDestroyer.destroy(this.workingDirectory, this.version);
}
catch (Exception ex) {
LOGGER.error(ex, "Working directory: ''{0}'' could not be destroyed", this.workingDirectory);
}
}
private static boolean connect(NativeTransportParser nativeTransport) {
if (!nativeTransport.isStarted()) {
return true;
}
InetAddress address = nativeTransport.getAddress();
if (nativeTransport.getSslPort() != null) {
return connect(address, nativeTransport.getPort()) && connect(address, nativeTransport.getSslPort());
}
return connect(address, nativeTransport.getPort());
}
private static boolean connect(RpcTransportParser rpcTransport) {
if (!rpcTransport.isStarted()) {
return true;
}
return connect(rpcTransport.getAddress(), rpcTransport.getPort());
}
private static boolean connect(InetAddress address, int port) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(address, port), 100);
return true;
}
catch (IOException ex) {
return false;
}
}
}