All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.stargate.sdk.grpc.GrpcClient Maven / Gradle / Ivy

package io.stargate.sdk.grpc;

import com.evanlennick.retry4j.CallExecutorBuilder;
import com.evanlennick.retry4j.Status;
import com.evanlennick.retry4j.config.RetryConfig;
import com.evanlennick.retry4j.config.RetryConfigBuilder;
import com.google.protobuf.*;
import io.stargate.grpc.StargateBearerToken;
import io.stargate.proto.QueryOuterClass;
import io.stargate.proto.StargateGrpc;
import io.stargate.sdk.api.ApiConstants;
import io.stargate.sdk.audit.ServiceCallObserver;
import io.stargate.sdk.grpc.audit.ServiceGrpcCallEvent;
import io.stargate.sdk.grpc.domain.BatchGrpc;
import io.stargate.sdk.grpc.domain.QueryGrpc;
import io.stargate.sdk.grpc.domain.ResultSetGrpc;
import io.stargate.sdk.grpc.utils.FuturesUtils;
import io.stargate.sdk.grpc.utils.StreamObserverToReactivePublisher;
import io.stargate.sdk.utils.CompletableFutures;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * Wrapping the HttpClient and provide helpers
 * 
 * @author Cedrick LUNVEN (@clunven)
 */
public class GrpcClient implements ApiConstants {

    /** Logger for our Client. */
    private static final Logger LOGGER = LoggerFactory.getLogger(GrpcClient.class);

    // -------------------------------------------
    // ----------------- Singleton ---------------
    // -------------------------------------------

    /** Singleton pattern. */
    private static GrpcClient _instance = null;

    /**
     * Hide default constructor
     */
    private GrpcClient() {}
    
    /**
     * Singleton Pattern.
     * 
     * @return
     *      singleton for the class
     */
    public static synchronized GrpcClient getInstance() {
        if (_instance == null) {
            _instance = new GrpcClient();
        }
        return _instance;
    }

    // -------------------------------------------
    // ----------------- Retries   ---------------
    // -------------------------------------------

    /** Default settings in Request and Retry */
    private static final int DEFAULT_RETRY_COUNT       = 3;

    /** Default settings in Request and Retry */
    private static final Duration DEFAULT_RETRY_DELAY  = Duration.ofMillis(100);

    /** Default retry configuration. */
    protected RetryConfig retryConfig = new RetryConfigBuilder()
            .retryOnAnyException()
            .withDelayBetweenTries(DEFAULT_RETRY_DELAY)
            .withExponentialBackoff()
            .withMaxNumberOfTries(DEFAULT_RETRY_COUNT)
            .build();

    /**
     * Set value for retryConfig
     *
     * @param retryConfig new value for retryConfig
     */
    public void setRetryConfig(RetryConfig retryConfig) {
        this.retryConfig = retryConfig;
    }

    // -------------------------------------------
    // ---------- Execute with GRPC --------------
    // -------------------------------------------

    /**
     * Execute a request coming from elsewhere.
     * 
     * @param sGrpc
     *      gRPC service
     * @param  query
     *      gRPC query
     * @param token
     *      authentication token
     * @return
     *      service response
     */
    public ResultSetGrpc execute(ServiceGrpc sGrpc, QueryGrpc query, String token) {
        // Initializing the invocation event
        ServiceGrpcCallEvent event = new ServiceGrpcCallEvent(sGrpc, query);
        // Create Stub
        StargateGrpc.StargateBlockingStub syncStub = StargateGrpc.newBlockingStub(sGrpc.getChannel())
                .withCallCredentials(new StargateBearerToken(token))
                .withDeadlineAfter(5, TimeUnit.SECONDS);
        try {
            // Execute
            Status< QueryOuterClass.Response> status = executeWithRetries(syncStub, mapGrpcQuery(query));
            // Audit Mapping
            event.setTotalTries(status.getTotalTries());
            event.setLastException(status.getLastExceptionThatCausedRetry());
            event.setResponseElapsedTime(status.getTotalElapsedDuration().toMillis());
            event.setResponseTimestamp(status.getEndTime());
            // Response Mapping
            return new ResultSetGrpc(status.getResult().getResultSet());
        } catch (RuntimeException e) {
            event.setErrorClass(e.getClass().getName());
            event.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onCall(event)));
        }
    }


    /**
     * Execute a batch coming from elsewhere.
     *
     * @param sGrpc
     *      gRPC service
     * @param  batch
     *      gRPC query
     * @param token
     *      authentication token
     * @return
     *      service response
     */
    public ResultSetGrpc executeBatch(ServiceGrpc sGrpc, BatchGrpc batch, String token) {
        // Initializing the invocation event
        ServiceGrpcCallEvent event = new ServiceGrpcCallEvent(sGrpc, batch);
        // Create Stub
        StargateGrpc.StargateBlockingStub syncStub = StargateGrpc.newBlockingStub(sGrpc.getChannel())
                .withCallCredentials(new StargateBearerToken(token))
                .withDeadlineAfter(5, TimeUnit.SECONDS);
        try {
            // Execute
            long top =- System.currentTimeMillis();
            QueryOuterClass.Response response = syncStub.executeBatch(mapGrpcBatch(batch));
            event.setResponseElapsedTime(System.currentTimeMillis() - top);
            event.setResponseTimestamp(event.getResponseElapsedTime());
            return new ResultSetGrpc(response.getResultSet());
        } catch (RuntimeException e) {
            event.setErrorClass(e.getClass().getName());
            event.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onCall(event)));
        }
    }

    /**
     * Execute a request coming from elsewhere.
     *
     * @param sGrpc
     *      gRPC service
     * @param  query
     *      gRPC query
     * @param token
     *      authentication token
     * @return
     *      service response
     */
    public CompletableFuture executeAsync(ServiceGrpc sGrpc, QueryGrpc query, String token) {
        ServiceGrpcCallEvent event = new ServiceGrpcCallEvent(sGrpc, query);
        StargateGrpc.StargateFutureStub futureStub = StargateGrpc.newFutureStub(sGrpc.getChannel())
                .withCallCredentials(new StargateBearerToken(token))
                .withDeadlineAfter(5, TimeUnit.SECONDS);
        long startTime = System.currentTimeMillis();
        try {
            return FuturesUtils
                    .asCompletableFuture(futureStub.executeQuery(mapGrpcQuery(query)))
                    .thenApply(res -> {
                        event.setResponseTime(System.currentTimeMillis() - startTime);
                        return new ResultSetGrpc(res.getResultSet());
                    });
        } catch (RuntimeException e) {
            event.setErrorClass(e.getClass().getName());
            event.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onCall(event)));
        }
    }

    /**
     * Execute a request coming from elsewhere.
     *
     * @param sGrpc
     *      gRPC service
     * @param  batch
     *      gRPC batch
     * @param token
     *      authentication token
     * @return
     *      service response
     */
    public CompletableFuture executeBatchAsync(ServiceGrpc sGrpc, BatchGrpc batch, String token) {
        ServiceGrpcCallEvent event = new ServiceGrpcCallEvent(sGrpc, batch);
        StargateGrpc.StargateFutureStub futureStub = StargateGrpc.newFutureStub(sGrpc.getChannel())
                .withCallCredentials(new StargateBearerToken(token))
                .withDeadlineAfter(5, TimeUnit.SECONDS);
        long startTime = System.currentTimeMillis();
        try {
            return FuturesUtils
                    .asCompletableFuture(futureStub.executeBatch(mapGrpcBatch(batch)))
                    .thenApply(res -> {
                        event.setResponseTime(System.currentTimeMillis() - startTime);
                        return new ResultSetGrpc(res.getResultSet());
                    });
        } catch (RuntimeException e) {
            event.setErrorClass(e.getClass().getName());
            event.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onCall(event)));
        }
    }

    /**
     * Execute a reactive query.
     *
     * @param sGrpc
     *      gRPC service
     * @param  query
     *      gRPC query
     * @param token
     *      authentication token
     * @return
     *      service response
     */
    public Mono executeReactive(ServiceGrpc sGrpc, QueryGrpc query, String token) {
        ServiceGrpcCallEvent event = new ServiceGrpcCallEvent(sGrpc, query);
        StargateGrpc.StargateStub reactiveStub = StargateGrpc.newStub(sGrpc.getChannel())
                .withCallCredentials(new StargateBearerToken(token))
                .withDeadlineAfter(5, TimeUnit.SECONDS);
        long startTime = System.currentTimeMillis();
        try {
            StreamObserverToReactivePublisher streamObserverPublisher = new StreamObserverToReactivePublisher();
            Mono mono = Mono.from(streamObserverPublisher);
            reactiveStub.executeQuery(mapGrpcQuery(query), streamObserverPublisher);
            return mono.map(res -> new ResultSetGrpc(res.getResultSet()));
        } catch (RuntimeException e) {
            event.setErrorClass(e.getClass().getName());
            event.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onCall(event)));
        }
    }

    /**
     * Execute a reactive query.
     *
     * @param sGrpc
     *      gRPC service
     * @param  batch
     *      gRPC batch
     * @param token
     *      authentication token
     * @return
     *      service response
     */
    public Mono executeBatchReactive(ServiceGrpc sGrpc, BatchGrpc batch, String token) {
        ServiceGrpcCallEvent event = new ServiceGrpcCallEvent(sGrpc, batch);
        StargateGrpc.StargateStub reactiveStub = StargateGrpc.newStub(sGrpc.getChannel())
                .withCallCredentials(new StargateBearerToken(token))
                .withDeadlineAfter(5, TimeUnit.SECONDS);
        long startTime = System.currentTimeMillis();
        try {
            StreamObserverToReactivePublisher streamObserverPublisher = new StreamObserverToReactivePublisher();
            Mono mono = Mono.from(streamObserverPublisher);
            reactiveStub.executeBatch(mapGrpcBatch(batch), streamObserverPublisher);
            return mono.map(res -> new ResultSetGrpc(res.getResultSet()));
        } catch (RuntimeException e) {
            event.setErrorClass(e.getClass().getName());
            event.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onCall(event)));
        }
    }

    /**
     * Map grpc Input base on the Grpc Query bean.
     *
     * @param batchGrpc
     *      current grpc batch
     * @return
     *      input Request forged
     */
    private QueryOuterClass.Batch mapGrpcBatch(BatchGrpc batchGrpc) {
        io.stargate.proto.QueryOuterClass.Batch.Builder batchBuilder = QueryOuterClass.Batch.newBuilder();

        QueryOuterClass.BatchParameters.Builder bbb = QueryOuterClass.BatchParameters.newBuilder();
        // Parameters
        //bbb.setConsistency();
        //bbb.setKeyspace();
        //bbb.setTimestamp();
        //bbb.setTracing();
        //batchBuilder.setParameters();

        QueryOuterClass.BatchQuery.newBuilder()
                .setCql("req1")
                .setValues(QueryOuterClass.Values.newBuilder().build());

        //for (String cqlQuery : batchQuery.) {
        //    batchBuilder.addQueries(
        //            QueryOuterClass.BatchQuery.newBuilder().setCql(cqlQuery).
        //                    build());
        //}
        //return getGrpcConnection()
        //        .getSyncStub()
        //        .executeBatch(batchBuilder.build());
        return null;
    }

    /**
     * Map grpc Input base on the Grpc Query bean.
     *
     * @param queryGrpc
     *      current grpc query
     * @return
     *      input Request forged
     */
    private QueryOuterClass.Query mapGrpcQuery(QueryGrpc queryGrpc) {
        // Core Query
        QueryOuterClass.Query.Builder queryBuilder = QueryOuterClass.Query.newBuilder();
        queryBuilder.setCql(queryGrpc.getCqlStatement().getCql());

        // Positional Values
        if (!queryGrpc.getCqlStatement().getPositionalValues().isEmpty()) {
            QueryOuterClass.Values.Builder valuesBuilder = QueryOuterClass.Values.newBuilder();
            queryGrpc.getCqlStatement().getPositionalValues().forEach(p -> valuesBuilder.addValues(mapGrpcValue(p)));
            queryBuilder.setValues(valuesBuilder);
        }

        // Named Values
        if (!queryGrpc.getCqlStatement().getNamedValues().isEmpty()) {
            QueryOuterClass.Values.Builder valuesBuilder = QueryOuterClass.Values.newBuilder();
            queryGrpc.getCqlStatement().getNamedValues().forEach((key, value) -> {
                valuesBuilder.addValueNames(key);
                valuesBuilder.addValues(mapGrpcValue(value));
            });
            queryBuilder.setValues(valuesBuilder);
        }

        // MetaData
        QueryOuterClass.QueryParameters.Builder queryParamsBuilder = QueryOuterClass.QueryParameters.newBuilder();
        if (null != queryGrpc.getConsistencyLevel()) {
            queryParamsBuilder.setConsistency(QueryOuterClass.ConsistencyValue
                    .newBuilder()
                    .setValue(queryGrpc.getConsistencyLevel()).build());
        }
        if (queryGrpc.getPageSize() > 0) {
            queryParamsBuilder.setPageSize(Int32Value.newBuilder()
                    .setValue(queryGrpc.getPageSize()).build());
        }
        if (queryGrpc.getPagingState() != null) {
            queryParamsBuilder.setPagingState(BytesValue.newBuilder()
                    .setValue(ByteString.copyFromUtf8(queryGrpc.getPagingState())).build());
        }
        if (queryGrpc.getKeyspace() != null) {
            queryParamsBuilder.setKeyspace(StringValue.newBuilder()
                    .setValue(queryGrpc.getKeyspace()));
        }
        if (queryGrpc.getTimestamp() > 0) {
            queryParamsBuilder.setTimestamp(Int64Value.of(queryGrpc.getTimestamp()));
        }
        if (queryGrpc.isTracing()) {
            queryParamsBuilder.setTracing(true);
        }
        queryBuilder.setParameters(queryParamsBuilder);
        return queryBuilder.build();
    }

    /**
     * Populate Parameter.
     *
     * @param o
     *      current param
     * @return
     *      current query
     */
    private QueryOuterClass.Value mapGrpcValue(Object o) {
        QueryOuterClass.Value.Builder vb = QueryOuterClass.Value.newBuilder();
        if (o instanceof String) {
            vb.setString((String) o);
        } else if (o instanceof Boolean){
            vb.setBoolean((Boolean) o);
        } else if (o instanceof byte[]) {
            vb.setBytes(ByteString.copyFrom((byte[]) o));
        } else if (o instanceof BigDecimal) {
            //vb.setDecimal(QueryOuterClass.Decimal.newBuilder().setValue(""));
        }
        /*
        vb.setDate()
        vb.setDecimal();
        vb.setDouble();
        vb.setFloat();
        vb.setInt();
        vb.setTime();

       vb.setNull();
       vb.setUnset();
       vb.setUuid();
       vb.setVarint();
       vb.setInet();

        vb.setCollection();
        vb.setUdt();*/
        return vb.build();
    }

    /**
     * Implementing retries.
     *
     * @param stub
     *      blocking stub
     * @return
     *      the closeable response
     */
    @SuppressWarnings("unchecked")
    private Status executeWithRetries(StargateGrpc.StargateBlockingStub stub, QueryOuterClass.Query grpcQuery) {
        return new CallExecutorBuilder()
                .config(retryConfig)
                .onSuccessListener(s -> CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onSuccess(s))))
                .onCompletionListener(s -> CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onCompletion(s))))
                .onFailureListener(s -> {
                    LOGGER.error("Calls failed after {} retries", s.getTotalTries());
                    CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onFailure(s)));
                })
                .afterFailedTryListener(s -> {
                    LOGGER.error("Failure on attempt {}/{} ", s.getTotalTries(), retryConfig.getMaxNumberOfTries());
                    LOGGER.error("Failed request {} on {}", grpcQuery.getCql() ,stub.getChannel().toString() );
                    LOGGER.error("+ Exception was ", s.getLastExceptionThatCausedRetry());
                    CompletableFuture.runAsync(()-> notifyAsync(listener->listener.onFailedTry(s)));
                })
                .build()
                .execute(() -> stub.executeQuery(grpcQuery));
    }

    // -------------------------------------------
    // ----------      AUDIT        --------------
    // -------------------------------------------

    /** Observers. */
    protected static Map apiInvocationsObserversMap = new ConcurrentHashMap<>();

    /**
     * Register a new listener.
     *
     * @param name
     *      current name
     * @param listener
     *      current listener
     */
    public static void registerListener(String name, ServiceCallObserver listener) {
        apiInvocationsObserversMap.put(name, listener);
    }

    /**
     * Asynchronously send calls to listener for tracing.
     *
     * @param lambda
     *      operations to execute
     * @return
     *      void
     */
    private void notifyAsync(Consumer lambda) {
        CompletableFutures.allDone(apiInvocationsObserversMap.values().stream()
                .map(l -> CompletableFuture.runAsync(() -> lambda.accept(l)))
                .collect(Collectors.toList()));
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy