package com.myorg.ripostemicroservicetemplate.endpoints;


import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;

import net.javacrumbs.futureconverter.java8guava.FutureConverter;

import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Named;


import static com.myorg.ripostemicroservicetemplate.error.ProjectApiError.EXAMPLE_EMBEDDED_CASSANDRA_DISABLED;
import static;

 * Endpoint that shows how to do Cassandra calls in an async way using the async driver utilities, without creating
 * extra threads to monitor futures/etc. This maximizes the async nonblocking functionality.
 * <p>NOTE: Don't let the volume of code in here throw you - a large portion of this class is for embedded cassandra
 * which wouldn't be necessary for a non-example project.
 * <p>TODO: EXAMPLE CLEANUP - Delete this class.
 * @author Nic Munroe
public class ExampleCassandraAsyncEndpoint extends StandardEndpoint<Void, String> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public static final String MATCHING_ENDPOINT_PATH = "/exampleCassandraAsync";
    private static final Matcher MATCHER = Matcher.match(MATCHING_ENDPOINT_PATH);

    private static final Statement basicCassandraQuery =
        new SimpleStatement("SELECT release_version FROM system.local");

    private final boolean disableCassandra;

    public ExampleCassandraAsyncEndpoint(@Named("disableCassandra") Boolean disableCassandra) {
        this.disableCassandra = disableCassandra;

        // Start up Cassandra as early as possible so it's ready when the first request comes in.
        try {
            // We have to specify the storagedir due to a cassandra-unit bug.
            //      See
            System.setProperty("cassandra.storagedir", "build/embeddedCassandra/storageDir");
        catch (Exception ex) {
            // No need to prevent the entire app from starting up if there are cassandra problems
            logger.error("Error during embedded cassandra startup", ex);

    public @NotNull CompletableFuture<ResponseInfo<String>> execute(
        @NotNull RequestInfo<Void> request,
        @NotNull Executor longRunningTaskExecutor,
        @NotNull ChannelHandlerContext ctx
    ) {
        Session session = EmbeddedCassandraUtils.cassandraSession(disableCassandra);
        if (session == null) {
            ApiError apiErrorToThrow = (disableCassandra)
                                       ? EXAMPLE_EMBEDDED_CASSANDRA_DISABLED
                                       : SampleCoreApiError.GENERIC_SERVICE_ERROR;

            throw ApiException.newBuilder()
                              .withExceptionMessage("Unable to get cassandra session.")

        ResultSetFuture cassandraResultFuture = session.executeAsync(basicCassandraQuery);

        // Convert the cassandra result future to a CompletableFuture, then add a listener that turns the result of the
        //      Cassandra call into the ResponseInfo<String> we need to return. Note that we're not doing
        //      thenApplyAsync() because the work done to translate the Cassandra result to our ResponseInfo object is
        //      trivial and doesn't need it's own thread. If you had more complex logic that was time consuming (or more
        //      blocking calls) you would want to do the extra work with CompletableFuture.*Async() calls.
        return FutureConverter
            .thenApply(functionWithTracingAndMdc(this::buildResponseFromCassandraQueryResult, ctx));

    private ResponseInfo<String> buildResponseFromCassandraQueryResult(ResultSet result) {"Building response for async cassandra request");
        return ResponseInfo
            .newBuilder("Cassandra query succeeded. Cassandra version: " +"release_version"))

    public @NotNull Matcher requestMatcher() {
        return MATCHER;

     * Contains some static utilities for starting an embedded Cassandra instance. Normally your Guice module would
     * configure whatever cassandra cluster/session you wanted (embedded or otherwise), and you'd {@code @Inject} the
     * custer/session into your endpoints as needed. But since this is just test/example code tied to
     * ExampleCassandraAsyncEndpoint, we want this code to get wiped away when ExampleCassandraAsyncEndpoint is
     * deleted.
    public static class EmbeddedCassandraUtils {

        private static final Logger logger = LoggerFactory.getLogger(EmbeddedCassandraUtils.class);

        private static final String embeddedClusterContactPointHost = "localhost";
        private static final int embeddedClusterContactPointPort = 9042;
        private static final String embeddedClusterWorkDirectory = "build/embeddedCassandra";
        private static final String cassandraYamlFile = "/embedded-cassandra.yaml";

        private static Session cassandraSession = null;

        // All access to this class should happen through the static methods.
        private EmbeddedCassandraUtils() { }
        private static Session startEmbeddedCassandra(boolean disableCassandra) {
            if (disableCassandra) {
                logger.warn("Embedded cassandra is NOT starting up because your app configuration explicitly requests "
                            + "that it be disabled.");
                return null;

            if (cassandraSession == null) {
                File cassandraWorkDir = new File(embeddedClusterWorkDirectory);
                String cassandraWorkDirAbsolutePath = cassandraWorkDir.getAbsolutePath();
                if (!cassandraWorkDir.exists()) {
          "Creating the  embedded Cassandra folders...{}", cassandraWorkDirAbsolutePath);
                    if (!cassandraWorkDir.mkdirs()) {
                        throw new RuntimeException("Unable to create working directory " + cassandraWorkDirAbsolutePath);
                // Start embedded cassandra
      "Finished Creating the  embedded Cassandra folders...{}", cassandraWorkDirAbsolutePath);
      "Starting embedded Cassandra");

                try {
                catch (Exception e) {
                    throw new RuntimeException(e);

                Cluster cassandraCluster = Cluster.builder()
                cassandraSession = cassandraCluster.connect();

            return cassandraSession;

        public static Session cassandraSession(boolean disableCassandra) {
            if (cassandraSession == null) {

            return cassandraSession;