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

com.turbospaces.gcp.pubsub.producer.DefaultPubsubPublisher Maven / Gradle / Ivy

package com.turbospaces.gcp.pubsub.producer;

import java.net.URI;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import com.google.cloud.spring.pubsub.core.publisher.PubSubPublisherTemplate;
import com.google.cloud.spring.pubsub.support.PublisherFactory;
import com.google.protobuf.Any;
import com.google.pubsub.v1.PubsubMessage;
import com.turbospaces.api.Topic;
import com.turbospaces.api.facade.NotificationWrapperFacade;
import com.turbospaces.api.facade.ResponseWrapperFacade;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.common.PlatformUtil;
import com.turbospaces.dispatch.TransactionalRequestOutcome;
import com.turbospaces.executor.PlatformExecutorService;
import com.turbospaces.http.HttpProto;

import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.gpubsub.impl.PubsubWithRoutingKeyMessageWriterImpl;
import io.vavr.Function0;
import reactor.blockhound.integration.DefaultBlockHoundIntegration;

public abstract class DefaultPubsubPublisher extends PubSubPublisherTemplate implements PubsubPublisher {
    protected final Logger log = LoggerFactory.getLogger(getClass());
    protected final ApplicationProperties props;
    protected final ThreadLocal flag = DefaultBlockHoundIntegration.FLAG;
    protected final ZoneId UTC = ZoneId.of("UTC");

    public DefaultPubsubPublisher(ApplicationProperties props, PublisherFactory producerFactory) throws Exception {
        super(producerFactory);
        this.props = Objects.requireNonNull(props);
    }
    @Override
    @SuppressWarnings("serial")
    public CompletableFuture publish(String topic, PubsubMessage record, BiConsumer callback) {
        Map mdc = MDC.getCopyOfContextMap(); // ~ capture MDC

        //
        // ~ temporary allow blocking call even though it is not blocking by API specification
        //
        return DefaultBlockHoundIntegration.allowBlockingUnchecked(new Function0<>() {
            @Override
            public CompletableFuture apply() {
                CompletableFuture future = publish(topic, record);
                return future.whenComplete((result, ex) -> {
                    PlatformExecutorService.propagete(mdc);
                    try {
                        callback.accept(result, ex);
                    } finally {
                        if (Objects.nonNull(mdc)) {
                            MDC.clear();
                        }
                    }
                });
            }
        });
    }
    @Override
    public CompletableFuture publish(String topic, PubsubMessage pubsubMessage) {
        var toReset = flag.get();
        try {
            flag.set(false);
            return super.publish(topic, pubsubMessage);
        } finally {
            flag.set(toReset);
        }
    }
    @Override
    public  CompletableFuture publish(String topic, T payload, Map headers) {
        var toReset = flag.get();
        try {
            flag.set(false);
            return super.publish(topic, payload, headers);
        } finally {
            flag.set(toReset);
        }
    }
    @Override
    public  CompletableFuture publish(String topic, T payload) {
        var toReset = flag.get();
        try {
            flag.set(false);
            return super.publish(topic, payload);
        } finally {
            flag.set(toReset);
        }
    }
    @Override
    public void publishReply(TransactionalRequestOutcome outcome) {
        ResponseWrapperFacade respw = outcome.getReply();
        if (Objects.nonNull(respw)) {
            String replyTo = respw.headers().getReplyTo();
            String typeUrl = respw.body().getTypeUrl();
            long now = System.currentTimeMillis();
            String messageId = respw.headers().getMessageId();
            String status = StringUtils.lowerCase(respw.status().errorCode().toString()).intern();

            if (StringUtils.isNotEmpty(replyTo)) {
                log.debug("about to post {} to {} ...", typeUrl, replyTo);

                var record = PubsubMessage.newBuilder();
                record.setData(respw.toByteString());
                if (Objects.nonNull(outcome.getKey())) {
                    record.setOrderingKey(outcome.getKey().toString());
                }
                record.putAttributes(HttpProto.HEADER_X_TIMESTAMP, String.valueOf(now));

                CloudEventBuilder eventTemplate = CloudEventBuilder.v1().withSource(URI.create(props.CLOUD_APP_ID.get()));
                CloudEventBuilder event = eventTemplate.newBuilder()
                        .withId(messageId)
                        .withTime(OffsetDateTime.ofInstant(Instant.ofEpochMilli(now), UTC))
                        .withType(typeUrl);

                var messageWriter = new PubsubWithRoutingKeyMessageWriterImpl(record);

                publish(replyTo, messageWriter.writeBinary(event.build()), new BiConsumer() {
                    @Override
                    public void accept(String result, Throwable ex) {
                        if (Objects.nonNull(ex)) {
                            log.error(ex.getMessage(), ex);
                        } else {
                            log.debug("OUT ::: ({}:{}:s-{}) has been accepted by pubsub (topic={}:messageId={},took={})",
                                    typeUrl,
                                    messageId,
                                    status,
                                    replyTo,
                                    result,
                                    System.currentTimeMillis() - now);
                        }
                    }
                });
            }
        }
    }
    @Override
    public void publishNotifications(Topic topic, TransactionalRequestOutcome outcome) {
        List notifications = outcome.getNotifications();
        List types = notifications.stream().map(NotificationWrapperFacade::body).map(Any::getTypeUrl).collect(Collectors.toList());
        log.debug("about to post {} to {} ...", types, topic.name());

        for (NotificationWrapperFacade it : notifications) {
            String typeUrl = it.body().getTypeUrl();
            long now = System.currentTimeMillis();

            var record = PubsubMessage.newBuilder();
            record.setData(it.toByteString());
            if (Objects.nonNull(outcome.getKey())) {
                record.setOrderingKey(outcome.getKey().toString());
            }

            CloudEventBuilder eventTemplate = CloudEventBuilder.v1().withSource(URI.create(props.CLOUD_APP_ID.get()));
            CloudEventBuilder event = eventTemplate.newBuilder()
                    .withId(PlatformUtil.randomUUID().toString())
                    .withTime(OffsetDateTime.ofInstant(Instant.ofEpochMilli(now), UTC))
                    .withType(typeUrl);

            var messageWriter = new PubsubWithRoutingKeyMessageWriterImpl(record);

            publish(topic.name().toString(), messageWriter.writeBinary(event.build()), new BiConsumer() {
                @Override
                public void accept(String result, Throwable ex) {
                    if (Objects.nonNull(ex)) {
                        log.error(ex.getMessage(), ex);
                    } else {
                        log.debug("OUT ::: ({}) has been accepted by pubsub (topic={}:messageId={},took={})",
                                typeUrl,
                                topic.name(),
                                result,
                                System.currentTimeMillis() - now);
                    }
                }
            });
        }
    }
    @Override
    public void publishEvents(Topic topic, TransactionalRequestOutcome outcome) {
        List eventStream = outcome.getEventStream();
        var types = eventStream.stream().map(Any::getTypeUrl).collect(Collectors.toList());
        log.debug("about to post {} to {} ...", types, topic.name());

        for (Any wrapper : eventStream) {
            String typeUrl = wrapper.getTypeUrl();
            long now = System.currentTimeMillis();

            var record = PubsubMessage.newBuilder();
            record.setData(wrapper.toByteString());
            if (Objects.nonNull(outcome.getKey())) {
                record.setOrderingKey(outcome.getKey().toString());
            }

            CloudEventBuilder eventTemplate = CloudEventBuilder.v1().withSource(URI.create(props.CLOUD_APP_ID.get()));
            CloudEventBuilder event = eventTemplate.newBuilder()
                    .withId(PlatformUtil.randomUUID().toString())
                    .withTime(OffsetDateTime.ofInstant(Instant.ofEpochMilli(now), UTC))
                    .withType(typeUrl);

            var messageWriter = new PubsubWithRoutingKeyMessageWriterImpl(record);

            publish(topic.name().toString(), messageWriter.writeBinary(event.build()), new BiConsumer() {
                @Override
                public void accept(String result, Throwable ex) {
                    if (Objects.nonNull(ex)) {
                        log.error(ex.getMessage(), ex);
                    } else {
                        log.debug("OUT ::: ({}) has been accepted by pubsub (topic={}:messageId={},took={})",
                                typeUrl,
                                topic.name(),
                                result,
                                System.currentTimeMillis() - now);
                    }
                }
            });
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy