info.archinnov.achilles.embedded.ServerStarter.java Source code

Java tutorial

Introduction

Here is the source code for info.archinnov.achilles.embedded.ServerStarter.java

Source

/*
 * 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.
 */

/**
 * Modified version of original class from HouseScream
    
 * https://github.com/housecream/server/blob/develop/server/ws/src/main/java/org/housecream/server/application/CassandraEmbedded.java
 */

package info.archinnov.achilles.embedded;

import static info.archinnov.achilles.embedded.AchillesCassandraConfig.*;
import static info.archinnov.achilles.embedded.CassandraEmbeddedConfigParameters.*;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.*;

import org.apache.cassandra.service.CassandraDaemon;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableSet;

import info.archinnov.achilles.exception.AchillesException;
import info.archinnov.achilles.type.TypedMap;
import info.archinnov.achilles.validation.Validator;

public enum ServerStarter {
    CASSANDRA_EMBEDDED;

    private static final Logger LOGGER = LoggerFactory.getLogger(ServerStarter.class);

    private static final OrderedShutdownHook orderedShutdownHook = new OrderedShutdownHook();
    private static int cqlPort;

    private static int thriftPort;
    private final Logger log = LoggerFactory.getLogger(ServerStarter.class);

    private static int storageRandomPort() {
        return PortFinder.findAvailableBetween(7001, 7500);
    }

    private static int storageSslRandomPort() {
        return PortFinder.findAvailableBetween(7501, 7999);
    }

    private static int jxmRandomPort() {
        return PortFinder.findAvailableBetween(7501, 7999);
    }

    private static int cqlRandomPort() {
        return PortFinder.findAvailableBetween(9043, 9499);
    }

    private static int thriftRandomPort() {
        return PortFinder.findAvailableBetween(9501, 9999);
    }

    public void startServer(String cassandraHost, TypedMap parameters) {
        if (StringUtils.isBlank(cassandraHost)) {

            log.debug("Do start embedded Cassandra server ");
            validateDataFolders(parameters);
            cleanCassandraDataFiles(parameters);
            randomizePortsIfNeeded(parameters);

            // Start embedded server
            CASSANDRA_EMBEDDED.start(parameters);
        }
    }

    public void checkAndConfigurePorts(TypedMap parameters) {
        log.trace("Check and configure Thrift/CQL ports");
        Integer cqlPort = parameters.getTyped(CASSANDRA_CQL_PORT);
        Integer thriftPort = parameters.getTyped(CASSANDRA_THRIFT_PORT);
        if (cqlPort != null && ServerStarter.cqlPort != cqlPort.intValue()) {
            throw new IllegalArgumentException(String.format(
                    "An embedded Cassandra server is already listening to CQL port '%s', the specified CQL port '%s' does not match",
                    ServerStarter.cqlPort, cqlPort));
        } else {
            parameters.put(CASSANDRA_CQL_PORT, ServerStarter.cqlPort);
        }

        if (thriftPort != null && ServerStarter.thriftPort != thriftPort.intValue()) {
            throw new IllegalArgumentException(String.format(
                    "An embedded Cassandra server is already listening to Thrift port '%s', the specified Thrift port '%s' does not match",
                    ServerStarter.thriftPort, thriftPort));
        } else {
            parameters.put(CASSANDRA_THRIFT_PORT, ServerStarter.thriftPort);
        }
    }

    public OrderedShutdownHook getShutdownHook() {
        return orderedShutdownHook;
    }

    private void start(final TypedMap parameters) {
        if (isAlreadyRunning()) {
            log.debug("Cassandra is already running, not starting new one");
            return;
        }

        final String triggersDir = createTriggersFolder();

        log.info(" Random embedded Cassandra RPC port/Thrift port = {}",
                parameters.<Integer>getTyped(CASSANDRA_THRIFT_PORT));
        log.info(" Random embedded Cassandra Native port/CQL port = {}",
                parameters.<Integer>getTyped(CASSANDRA_CQL_PORT));
        log.info(" Random embedded Cassandra Storage port = {}",
                parameters.<Integer>getTyped(CASSANDRA_STORAGE_PORT));
        log.info(" Random embedded Cassandra Storage SSL port = {}",
                parameters.<Integer>getTyped(CASSANDRA_STORAGE_SSL_PORT));
        log.info(" Random embedded Cassandra Remote JMX port = {}",
                System.getProperty("com.sun.management.jmxremote.port", "null"));
        log.info(" Embedded Cassandra triggers directory = {}", triggersDir);

        log.info("Starting Cassandra...");

        System.setProperty("cassandra.triggers_dir", triggersDir);
        System.setProperty("cassandra-foreground", "true");
        System.setProperty("cassandra.embedded.concurrent.reads",
                parameters.getTypedOr(CASSANDRA_CONCURRENT_READS, 32).toString());
        System.setProperty("cassandra.embedded.concurrent.writes",
                parameters.getTypedOr(CASSANDRA_CONCURRENT_WRITES, 32).toString());
        System.setProperty("cassandra-foreground", "true");

        final boolean useUnsafeCassandra = parameters.getTyped(USE_UNSAFE_CASSANDRA_DAEMON);

        if (useUnsafeCassandra) {
            System.setProperty("cassandra-num-tokens", "1");
        }

        System.setProperty("cassandra.config.loader", "info.archinnov.achilles.embedded.AchillesCassandraConfig");

        final CountDownLatch startupLatch = new CountDownLatch(1);
        final ExecutorService executor = Executors.newSingleThreadExecutor();
        final AtomicReference<CassandraDaemon> daemonRef = new AtomicReference<>();
        executor.execute(() -> {
            if (useUnsafeCassandra) {
                LOGGER.warn(
                        "******* WARNING, starting unsafe embedded Cassandra deamon. This should be only used for unit testing or development and not for production !");
            }

            CassandraDaemon cassandraDaemon = useUnsafeCassandra == true ? new AchillesCassandraDaemon()
                    : new CassandraDaemon();

            cassandraDaemon.completeSetup();
            cassandraDaemon.activate();
            daemonRef.getAndSet(cassandraDaemon);
            startupLatch.countDown();
        });

        try {
            startupLatch.await(30, SECONDS);
        } catch (InterruptedException e) {
            log.error("Timeout starting Cassandra embedded", e);
            throw new IllegalStateException("Timeout starting Cassandra embedded", e);
        }

        // Generate an OrderedShutdownHook to shutdown all connections from java clients before closing the server
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                log.info("Calling stop on Embedded Cassandra server");
                daemonRef.get().stop();

                log.info("Calling shutdown on all Cluster instances");
                // First call shutdown on all registered Java driver Cluster instances
                orderedShutdownHook.callShutDown();

                log.info("Shutting down embedded Cassandra server");
                // Then shutdown the server
                executor.shutdownNow();
            }
        });
    }

    private void validateDataFolders(Map<String, Object> parameters) {
        final String dataFolder = (String) parameters.get(DATA_FILE_FOLDER);
        final String commitLogFolder = (String) parameters.get(COMMIT_LOG_FOLDER);
        final String savedCachesFolder = (String) parameters.get(SAVED_CACHES_FOLDER);
        final String hintsFolder = (String) parameters.get(HINTS_FOLDER);

        log.debug(" Embedded Cassandra data directory = {}", dataFolder);
        log.debug(" Embedded Cassandra commitlog directory = {}", commitLogFolder);
        log.debug(" Embedded Cassandra saved caches directory = {}", savedCachesFolder);
        log.debug(" Embedded Cassandra hints directory = {}", hintsFolder);

        validateFolder(dataFolder);
        validateFolder(commitLogFolder);
        validateFolder(savedCachesFolder);
        validateFolder(hintsFolder);

        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_DATA_FOLDER, dataFolder);
        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_COMMITLOG_FOLDER, commitLogFolder);
        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_SAVED_CACHES_FOLDER, savedCachesFolder);
        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_HINTS_FOLDER, hintsFolder);

    }

    private void validateFolder(String folderPath) {
        String currentUser = System.getProperty("user.name");
        final File folder = new File(folderPath);
        if (!DEFAULT_ACHILLES_TEST_FOLDERS.contains(folderPath)) {
            Validator.validateTrue(folder.exists(), "Folder '%s' does not exist", folder.getAbsolutePath());
            Validator.validateTrue(folder.isDirectory(), "Folder '%s' is not a directory",
                    folder.getAbsolutePath());
            Validator.validateTrue(folder.canRead(),
                    "No read credential. Please grant read permission for the current user '%s' on folder '%s'",
                    currentUser, folder.getAbsolutePath());
            Validator.validateTrue(folder.canWrite(),
                    "No write credential. Please grant write permission for the current user '%s' on folder '%s'",
                    currentUser, folder.getAbsolutePath());
        } else if (!folder.exists()) {
            try {
                log.info("Creating folder : " + folder.getAbsolutePath());
                FileUtils.forceMkdir(folder);
            } catch (IOException e) {
                throw new RuntimeException("Cannot create Cassandra data folder " + folderPath, e);
            }
        } else {
            log.info("Using existing data folder for unit tests : " + folder.getAbsolutePath());
        }
    }

    private void cleanCassandraDataFiles(TypedMap parameters) {
        if (parameters.<Boolean>getTyped(CLEAN_CASSANDRA_DATA_FILES)) {
            final ImmutableSet<String> dataFolders = ImmutableSet.<String>builder()
                    .add(parameters.<String>getTyped(DATA_FILE_FOLDER))
                    .add(parameters.<String>getTyped(COMMIT_LOG_FOLDER))
                    .add(parameters.<String>getTyped(SAVED_CACHES_FOLDER)).build();
            for (String dataFolder : dataFolders) {
                File dataFolderFile = new File(dataFolder);
                if (dataFolderFile.exists() && dataFolderFile.isDirectory()) {
                    log.info("Cleaning up embedded Cassandra data directory '{}'",
                            dataFolderFile.getAbsolutePath());
                    try {
                        FileUtils.cleanDirectory(dataFolderFile);
                    } catch (IOException e) {
                        throw new AchillesException(String.format("Cannot clean data folder %s", dataFolder));
                    }
                }
            }
        }
    }

    private void randomizePortsIfNeeded(TypedMap parameters) {
        final Integer thriftPort = extractAndValidatePort(
                Optional.ofNullable(parameters.get(CASSANDRA_THRIFT_PORT)).orElse(thriftRandomPort()),
                CASSANDRA_THRIFT_PORT);
        final Integer cqlPort = extractAndValidatePort(
                Optional.ofNullable(parameters.get(CASSANDRA_CQL_PORT)).orElse(cqlRandomPort()),
                CASSANDRA_CQL_PORT);
        final Integer storagePort = extractAndValidatePort(
                Optional.ofNullable(parameters.get(CASSANDRA_STORAGE_PORT)).orElse(storageRandomPort()),
                CASSANDRA_STORAGE_PORT);
        final Integer storageSSLPort = extractAndValidatePort(
                Optional.ofNullable(parameters.get(CASSANDRA_STORAGE_SSL_PORT)).orElse(storageSslRandomPort()),
                CASSANDRA_STORAGE_SSL_PORT);

        final Integer jmxPort = extractAndValidatePort(
                Optional.ofNullable(parameters.get(CASSANDRA_JMX_PORT)).orElse(jxmRandomPort()),
                CASSANDRA_JMX_PORT);

        parameters.put(CASSANDRA_THRIFT_PORT, thriftPort);
        parameters.put(CASSANDRA_CQL_PORT, cqlPort);
        parameters.put(CASSANDRA_STORAGE_PORT, storagePort);
        parameters.put(CASSANDRA_STORAGE_SSL_PORT, storageSSLPort);

        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_THRIFT_PORT, thriftPort.toString());
        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_CQL_PORT, cqlPort.toString());
        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_STORAGE_PORT, storagePort.toString());
        System.setProperty(ACHILLES_EMBEDDED_CASSANDRA_STORAGE_SSL_PORT, storageSSLPort.toString());
        System.setProperty("com.sun.management.jmxremote.port", jmxPort.toString());
        System.setProperty("cassandra.skip_wait_for_gossip_to_settle", "0");

        ServerStarter.cqlPort = cqlPort;
        ServerStarter.thriftPort = thriftPort;
    }

    private Integer extractAndValidatePort(Object port, String portLabel) {
        Validator.validateTrue(port instanceof Integer, "The provided '%s' port should be an integer", portLabel);
        Validator.validateTrue((Integer) port > 0, "The provided '%s' port should positive", portLabel);
        return (Integer) port;

    }

    private String createTriggersFolder() {
        log.trace("Create triggers folder");
        final File triggersDir = new File(DEFAULT_ACHILLES_TEST_TRIGGERS_FOLDER);
        if (!triggersDir.exists()) {
            triggersDir.mkdir();
        }
        return triggersDir.getAbsolutePath();
    }

    private boolean isAlreadyRunning() {
        log.trace("Check whether an embedded Cassandra is already running");
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        try {
            MBeanInfo mBeanInfo = mbs.getMBeanInfo(new ObjectName("org.apache.cassandra.db:type=StorageService"));
            if (mBeanInfo != null) {
                return true;
            }
            return false;
        } catch (InstanceNotFoundException e) {
            return false;
        } catch (IntrospectionException e) {
            throw new IllegalStateException("Cannot check if cassandra is already running", e);
        } catch (MalformedObjectNameException e) {
            throw new IllegalStateException("Cannot check if cassandra is already running", e);
        } catch (ReflectionException e) {
            throw new IllegalStateException("Cannot check if cassandra is already running", e);
        }

    }
}