com.turbospaces.gcp.pubsub.producer.DefaultPubsubPublisher Maven / Gradle / Ivy
The newest version!
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.allowBlocking(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);
}
}
});
}
}
}