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

com.turbospaces.gcp.pubsub.consumer.PubsubRequestConsumer Maven / Gradle / Ivy

The newest version!
package com.turbospaces.gcp.pubsub.consumer;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.BooleanUtils;

import com.google.cloud.spring.pubsub.support.BasicAcknowledgeablePubsubMessage;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.Any;
import com.google.protobuf.Message;
import com.turbospaces.api.Topic;
import com.turbospaces.api.facade.RequestWrapperFacade;
import com.turbospaces.api.jpa.CompositeStackTracer;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.common.PlatformUtil;
import com.turbospaces.dispatch.AbstractServerRequestConsumer;
import com.turbospaces.dispatch.SafeRequestHandler;
import com.turbospaces.dispatch.TransactionalRequestHandler;
import com.turbospaces.dispatch.TransactionalRequestOutcome;
import com.turbospaces.executor.WorkUnit;
import com.turbospaces.gcp.pubsub.PubsubRecord;
import com.turbospaces.gcp.pubsub.PubsubTransactionalRequest;
import com.turbospaces.gcp.pubsub.PubsubWorkUnit;
import com.turbospaces.mdc.MdcTags;
import com.turbospaces.rpc.QueuePostTemplate;

import api.v1.ApiFactory;
import io.micrometer.core.instrument.MeterRegistry;
import io.netty.util.AsciiString;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import jakarta.inject.Inject;

public class PubsubRequestConsumer extends AbstractServerRequestConsumer implements Consumer {
    private final QueuePostTemplate postTemplate;
    private final Tracer tracer;
    private final MeterRegistry meterRegistry;
    private final Topic topic;
    private final PubsubContextWorkerFactory workerFactory;
    private final Map> handlers;

    @Inject
    public PubsubRequestConsumer(
            ApplicationProperties props,
            Tracer tracer,
            MeterRegistry meterRegistry,
            Topic topic,
            ApiFactory apiFactory,
            CompositeStackTracer stackTracer,
            QueuePostTemplate postTemplate,
            List> handlers,
            PubsubContextWorkerFactory workerFactory) {
        super(props, apiFactory, stackTracer);
        this.postTemplate = Objects.requireNonNull(postTemplate);
        this.meterRegistry = Objects.requireNonNull(meterRegistry);
        this.tracer = Objects.requireNonNull(tracer);
        this.topic = Objects.requireNonNull(topic);
        this.workerFactory = Objects.requireNonNull(workerFactory);
        this.handlers = ImmutableMap.copyOf(handlers.stream().collect(
                Collectors.toMap(h -> {
                    Message defaultInstance = com.google.protobuf.Internal.getDefaultInstance(h.requestType());
                    Any any = Any.pack(defaultInstance);
                    return any.getTypeUrl();
                }, Function.identity())));
    }
    @Override
    public void accept(BasicAcknowledgeablePubsubMessage message) {
        PubsubWorkUnit unit = new PubsubRecord(topic, message);

        Optional> completable = Optional.empty();
        CountDownLatch latch = new CountDownLatch(1);

        try {
            var decoder = apiFactory.requestMapper();
            var reqw = decoder.unpack(unit);
            completable = logAndAccept(reqw, unit, latch);
            if (completable.isEmpty()) {
                unit.ack();
            }
        } catch (Throwable err) {
            logger.error(err.getMessage(), err);
            latch.countDown();
            completable = Optional.of(convertUnhandledException(unit, err));
        }

        if (completable.isPresent()) {
            FluentFuture.from(completable.get()).addCallback(new PublishOperationOutcomeCallback<>(), MoreExecutors.directExecutor());
        }
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public ListenableFuture schedule(WorkUnit unit, RequestWrapperFacade reqw, CountDownLatch latch) throws Throwable {
        var record = (PubsubWorkUnit) unit;
        var body = reqw.body();
        var span = span(record, reqw);
        var typeUrl = body.getTypeUrl();
        var handler = Objects.requireNonNull(handlers.get(typeUrl), "unable to locate handler for type: " + typeUrl);

        var callbackExecutor = MoreExecutors.directExecutor();
        var worker = workerFactory.worker(record);
        var tx = new PubsubTransactionalRequest<>(handler.requestType(), handler.newReply(), unit, reqw, latch);
        var sh = new SafeRequestHandler(meterRegistry, tracer, reqw, apiFactory, stackTracer, span, record, tx, handler);
        var callback = new FutureCallback<>() {
            @Override
            public void onSuccess(Object result) {
                //
                // ~ access executor not to clean it up, in case there are pending long running tasks in queue
                //
                worker.actor(unit, handler.actorIsRequired());
            }
            @Override
            public void onFailure(Throwable t) {
                logger.error(t.getMessage(), t);
            }
        };

        if (handler.isImmediateAcknowledge()) {
            logger.debug("got request from topic: {}, about to early acknowledge: {}", topic.name(), unit);
            sh.getTransaction().ack();
        } else if (BooleanUtils.isFalse(handler.actorIsRequired())) {
            logger.debug("got non-sequential request from topic: {}, about to early acknowledge: {}", topic.name(), unit);
            sh.getTransaction().ack();
        }

        //
        // ~ should not happen
        //
        if (handler.actorIsRequired() && Objects.isNull(unit.key())) {
            throw new IllegalArgumentException("no routing key provided for " + unit.toString());
        }

        var actor = worker.actor(unit, handler.actorIsRequired());
        FluentFuture.from(actor.submit(sh)).addCallback(callback, callbackExecutor);
        return sh.get();
    }

    private Span span(PubsubWorkUnit record, RequestWrapperFacade reqw) {
        var body = reqw.body();
        var operation = PlatformUtil.toLowerUnderscore(body.getTypeUrl());

        //
        // ~ extract tracing information
        //
        var parentScope = tracer.extract(Format.Builtin.TEXT_MAP, new PubsubSpanContextExtractor(record));

        //
        // ~ create child span
        //
        var buildSpan = tracer.buildSpan(operation);
        if (parentScope != null) {
            buildSpan = buildSpan.asChildOf(parentScope);
        }

        var span = buildSpan.start();
        span.setTag(MdcTags.MDC_MESSAGE_ID, reqw.headers().getMessageId());

        if (Objects.nonNull(record.key())) {
            AsciiString partitionKey = new AsciiString(record.key());
            span.setTag(MdcTags.MDC_ROUTING_KEY, partitionKey.toString());
        }

        return span;
    }

    private class PublishOperationOutcomeCallback implements FutureCallback {
        @Override
        public void onSuccess(TransactionalRequestOutcome result) {
            try {
                postTemplate.publishReply(result);
                postTemplate.publishNotifications(apiFactory.notifyTopic(), result);
                postTemplate.publishEvents(apiFactory.eventsTopic(), result);
            } catch (Exception err) {
                logger.atError().setCause(err).log();
            }
        }
        @Override
        public void onFailure(Throwable t) {
            try {

            } finally {

            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy