Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.turbospaces.gcp.pubsub.config.PubsubInitializer Maven / Gradle / Ivy
package com.turbospaces.gcp.pubsub.config;
import java.util.List;
import java.util.Objects;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import com.google.api.gax.rpc.ApiException;
import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.cloud.spring.pubsub.PubSubAdmin;
import com.google.cloud.spring.pubsub.core.subscriber.PubSubSubscriberTemplate;
import com.google.protobuf.Duration;
import com.google.protobuf.FieldMask;
import com.google.pubsub.v1.DeadLetterPolicy;
import com.google.pubsub.v1.ExpirationPolicy;
import com.google.pubsub.v1.RetryPolicy;
import com.google.pubsub.v1.Subscription;
import com.google.pubsub.v1.Topic;
import com.google.pubsub.v1.UpdateSubscriptionRequest;
import com.turbospaces.api.jpa.CompositeStackTracer;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.dispatch.TransactionalRequestHandler;
import com.turbospaces.gcp.pubsub.PubsubTopic;
import com.turbospaces.gcp.pubsub.consumer.PubsubContextWorkerFactory;
import com.turbospaces.gcp.pubsub.consumer.PubsubReplyTopicConsumer;
import com.turbospaces.gcp.pubsub.consumer.PubsubRequestTopicConsumer;
import com.turbospaces.rpc.DefaultRequestReplyMapper;
import com.turbospaces.rpc.QueuePostTemplate;
import api.v1.ApiFactory;
import io.micrometer.core.instrument.MeterRegistry;
import io.opentracing.Tracer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PubsubInitializer implements InitializingBean {
private static final String DLQ_POSTFIX = "dlq";
private static final int MAX_RETRY_DLQ_CREATION_ATTEMPTS = 3;
private final ApplicationProperties props;
private final MeterRegistry meterRegistry;
private final Tracer tracer;
private final List topics;
private final PubSubSubscriberTemplate subscriberTemplate;
private final QueuePostTemplate> postTemplate;
private final ApiFactory apiFactory;
private final CompositeStackTracer stackTracer;
private final DefaultRequestReplyMapper mapper;
private final List> handlers;
private final PubsubContextWorkerFactory workerFactory;
private final PubSubAdmin admin;
private final SubscriptionAdminClient subscriptionAdminClient;
public PubsubInitializer(
ApplicationProperties props,
Tracer tracer,
MeterRegistry meterRegistry,
List topics,
PubSubSubscriberTemplate subscriberTemplate,
QueuePostTemplate> postTemplate,
ApiFactory apiFactory,
CompositeStackTracer stackTracer,
DefaultRequestReplyMapper mapper,
List> handlers, PubsubContextWorkerFactory workerFactory,
PubSubAdmin admin, SubscriptionAdminClient subscriptionAdmin) {
this.props = Objects.requireNonNull(props);
this.tracer = Objects.requireNonNull(tracer);
this.meterRegistry = Objects.requireNonNull(meterRegistry);
this.topics = Objects.requireNonNull(topics);
this.subscriberTemplate = Objects.requireNonNull(subscriberTemplate);
this.postTemplate = Objects.requireNonNull(postTemplate);
this.apiFactory = Objects.requireNonNull(apiFactory);
this.stackTracer = Objects.requireNonNull(stackTracer);
this.mapper = Objects.requireNonNull(mapper);
this.handlers = Objects.requireNonNull(handlers);
this.workerFactory = Objects.requireNonNull(workerFactory);
this.admin = Objects.requireNonNull(admin);
this.subscriptionAdminClient = Objects.requireNonNull(subscriptionAdmin);
}
@Override
public void afterPropertiesSet() {
try {
for (PubsubTopic topic : topics) {
createTopicAndSubscriptionIfNeeded(topic);
if (topic.withCustomConfiguration()) {
return;
}
if (topic.isResponseTopic()) {
registerReplyTopicConsumer(topic);
} else {
registerRequestTopicConsumer(topic);
}
}
} catch (Throwable err) {
throw new BeanCreationException("unable to create pubsub auto configuration", err);
}
}
private void registerReplyTopicConsumer(PubsubTopic pubsubTopic) throws Throwable {
var replyConsumer = new PubsubReplyTopicConsumer(props, meterRegistry, pubsubTopic, subscriberTemplate, mapper, workerFactory, apiFactory);
replyConsumer.subscribe();
}
private void registerRequestTopicConsumer(PubsubTopic pubsubTopic) throws Throwable {
var requestConsumer = new PubsubRequestTopicConsumer(
props,
tracer,
meterRegistry,
pubsubTopic,
subscriberTemplate,
apiFactory,
stackTracer,
postTemplate,
handlers,
workerFactory);
requestConsumer.subscribe();
}
private void createTopicAndSubscriptionIfNeeded(PubsubTopic topicDefinition) {
String topicName = topicDefinition.name().toString();
if (Objects.isNull(admin.getTopic(topicName))) {
log.info("created topic: {}", admin.createTopic(topicName).getName());
}
if (topicDefinition.withCustomConfiguration()) {
log.debug("Skip autoconfiguration for topic: {}", topicDefinition.name().toString());
return;
}
String appId = props.CLOUD_APP_ID.get();
boolean recreateSubscriptions = props.PUBSUB_RECREATE_SUBSCRIPTIONS.get();
boolean subscriptionExists;
String subscriptionName = topicDefinition.subscriptionName(props);
Subscription subscription = admin.getSubscription(subscriptionName);
subscriptionExists = Objects.nonNull(subscription);
if (!subscriptionExists || recreateSubscriptions) {
Topic dlq = getDeadLetterTopic(topicName, appId, admin);
var subscriptionBuilder = Subscription.newBuilder()
.setName(subscriptionName)
.setTopic(topicName)
.setMessageRetentionDuration(
Duration.newBuilder().setSeconds(props.PUBSUB_SUBSCRIPTION_MESSAGE_RETENTION_DURATION.get().getSeconds()))
.setEnableExactlyOnceDelivery(props.PUBSUB_SUBSCRIPTION_EXACTLY_ONCE_DELIVERY_ENABLED.get())
.setRetainAckedMessages(props.PUBSUB_SUBSCRIPTION_RETAIN_ACKED_MESSAGES.get())
.setDeadLetterPolicy(DeadLetterPolicy.newBuilder()
.setDeadLetterTopic(dlq.getName())
.setMaxDeliveryAttempts(props.PUBSUB_DEADLETTER_DELIVERY_ATTEMPT_MAX.get())
.build())
.setRetryPolicy(RetryPolicy.newBuilder()
.setMinimumBackoff(Duration.newBuilder()
.setSeconds(props.PUBSUB_SUBSCRIPTION_RETRY_BACKOFF_MIN.get().toSeconds())
.build())
.setMaximumBackoff(Duration.newBuilder()
.setSeconds(props.PUBSUB_SUBSCRIPTION_RETRY_BACKOFF_MAX.get().toSeconds())
.build())
.build())
.setEnableMessageOrdering(props.PUBSUB_MESSAGE_ORDERING_ENABLED.get());
if (topicDefinition.withCustomAckDeadline()) {
subscriptionBuilder.setAckDeadlineSeconds((int) props.PUBSUB_ACK_DEADLINE.get().toSeconds());
}
if (recreateSubscriptions && subscriptionExists) {
log.info("removing subscription: {}", subscriptionName);
admin.deleteSubscription(subscriptionName);
}
log.info("created subscription: {}", admin.createSubscription(subscriptionBuilder));
}
if (subscriptionExists) {
updateSubscriptionSettingsIfChanged(subscriptionAdminClient, subscription);
}
}
protected Topic getDeadLetterTopic(String topicName, String appId, PubSubAdmin client) {
int retryAttempts = 0;
String deadLetterTopic = String.format("%s-%s", topicName, DLQ_POSTFIX);
String deadLetterSubscription = String.format("%s-%s-%s", topicName, appId, DLQ_POSTFIX);
Topic dlq = null;
do {
try {
if (Objects.isNull(client.getTopic(deadLetterTopic))) {
dlq = client.createTopic(deadLetterTopic);
} else {
dlq = client.getTopic(deadLetterTopic);
}
if (Objects.isNull(client.getSubscription(deadLetterSubscription))) {
client.createSubscription(Subscription.newBuilder()
.setName(deadLetterSubscription)
.setTopic(deadLetterTopic)
.setExpirationPolicy(ExpirationPolicy.newBuilder().build()));
}
} catch (RuntimeException e) {
log.error("Exception when creating dead letter topic/subscription", e);
retryAttempts++;
}
} while (dlq == null && retryAttempts < MAX_RETRY_DLQ_CREATION_ATTEMPTS);
if (dlq == null) {
// Handle the case where the topic creation still failed after retries
throw new RuntimeException("Failed to create or get the dead letter topic/subscription" +
" after multiple attempts.");
}
return dlq;
}
private void updateSubscriptionSettingsIfChanged(SubscriptionAdminClient adminClient, Subscription subscription) {
var subscriptionBuilder = Subscription.newBuilder(subscription);
var fieldMask = FieldMask.newBuilder();
boolean configurationChanged = false;
var exactlyOnceDelivery = props.PUBSUB_SUBSCRIPTION_EXACTLY_ONCE_DELIVERY_ENABLED.get();
if (subscription.getEnableExactlyOnceDelivery() != exactlyOnceDelivery) {
subscriptionBuilder.setEnableExactlyOnceDelivery(exactlyOnceDelivery);
fieldMask.addPaths("enable_exactly_once_delivery");
configurationChanged = true;
log.debug("{}: enable_exactly_once_delivery {} => {}",
subscription.getName(),
subscription.getEnableExactlyOnceDelivery(),
exactlyOnceDelivery);
}
boolean retainAckedMessages = props.PUBSUB_SUBSCRIPTION_RETAIN_ACKED_MESSAGES.get();
if (subscription.getRetainAckedMessages() != retainAckedMessages) {
subscriptionBuilder.setRetainAckedMessages(retainAckedMessages);
fieldMask.addPaths("retain_acked_messages");
configurationChanged = true;
log.debug("{}: retain_acked_messages {} => {}",
subscription.getName(),
subscription.getRetainAckedMessages(),
retainAckedMessages);
}
long messageRetentionSeconds = props.PUBSUB_SUBSCRIPTION_MESSAGE_RETENTION_DURATION.get().getSeconds();
if (subscription.getMessageRetentionDuration().getSeconds() != messageRetentionSeconds) {
Duration newMessageRetention = Duration.newBuilder().setSeconds(messageRetentionSeconds).build();
subscriptionBuilder.setMessageRetentionDuration(newMessageRetention);
fieldMask.addPaths("message_retention_duration");
configurationChanged = true;
log.debug("{}: message_retention_duration {} => {}",
subscription.getName(),
subscription.getMessageRetentionDuration().getSeconds(),
messageRetentionSeconds);
}
var ackDeadlineSeconds = (int) props.PUBSUB_ACK_DEADLINE.get().toSeconds();
if (subscription.getAckDeadlineSeconds() != ackDeadlineSeconds) {
subscriptionBuilder.setAckDeadlineSeconds(ackDeadlineSeconds);
fieldMask.addPaths("ack_deadline_seconds");
configurationChanged = true;
log.debug("{}: ack_deadline_seconds {} => {}",
subscription.getName(),
subscription.getAckDeadlineSeconds(),
ackDeadlineSeconds);
}
if (configurationChanged) {
var request = UpdateSubscriptionRequest.newBuilder().setSubscription(subscriptionBuilder).setUpdateMask(fieldMask);
try {
Subscription updatedSubscription = adminClient.updateSubscription(request.build());
log.info("updated subscription: {}", updatedSubscription);
} catch (ApiException e) {
log.error("Failed to apply subscription updates", e);
}
}
}
}