com.turbospaces.gcp.pubsub.config.PubsubSpringDiModule Maven / Gradle / Ivy
package com.turbospaces.gcp.pubsub.config;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.Executor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.DynamicCloud;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.threeten.bp.Duration;
import com.google.api.gax.batching.BatchingSettings;
import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.core.ExecutorProvider;
import com.google.api.gax.core.FixedExecutorProvider;
import com.google.api.gax.core.NoCredentialsProvider;
import com.google.api.gax.grpc.GrpcTransportChannel;
import com.google.api.gax.rpc.FixedTransportChannelProvider;
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.auth.Credentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.cloud.pubsub.v1.SubscriptionAdminSettings;
import com.google.cloud.pubsub.v1.TopicAdminClient;
import com.google.cloud.pubsub.v1.TopicAdminSettings;
import com.google.cloud.pubsub.v1.stub.PublisherStubSettings;
import com.google.cloud.pubsub.v1.stub.SubscriberStubSettings;
import com.google.cloud.spring.core.DefaultGcpProjectIdProvider;
import com.google.cloud.spring.core.GcpProjectIdProvider;
import com.google.cloud.spring.pubsub.PubSubAdmin;
import com.google.cloud.spring.pubsub.core.PubSubConfiguration;
import com.google.cloud.spring.pubsub.core.PubSubException;
import com.google.cloud.spring.pubsub.core.subscriber.PubSubSubscriberTemplate;
import com.google.cloud.spring.pubsub.support.CachingPublisherFactory;
import com.google.cloud.spring.pubsub.support.DefaultPublisherFactory;
import com.google.cloud.spring.pubsub.support.DefaultSubscriberFactory;
import com.google.cloud.spring.pubsub.support.PublisherFactory;
import com.google.cloud.spring.pubsub.support.SubscriberFactory;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.common.PlatformUtil;
import com.turbospaces.ups.RawServiceInfo;
import com.turbospaces.ups.UPSs;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
@Configuration
public class PubsubSpringDiModule implements DisposableBean {
protected String url(DynamicCloud cloud) throws IOException {
RawServiceInfo si = UPSs.findRequiredServiceInfoByName(cloud, UPSs.PUBSUB);
return si.read();
}
@Bean
public GcpProjectIdProvider gcpProjectIdProvider(ApplicationProperties props) {
if (props.isDevMode()) {
return () -> "local-project-id";
}
return new DefaultGcpProjectIdProvider();
}
@Bean
public CredentialsProvider credentialsProvider(DynamicCloud cloud) throws IOException {
Optional si = UPSs.findServiceInfoByName(cloud, UPSs.UPS_GCV);
if (si.isPresent()) {
Credentials credentials = GoogleCredentials.fromStream(si.get().toByteSource().openBufferedStream());
return () -> credentials;
}
return NoCredentialsProvider.create();
}
@Bean
public PubSubConfiguration pubSubConfiguration(ApplicationProperties props, GcpProjectIdProvider projectIdProvider) {
var config = new PubSubConfiguration();
config.initialize(projectIdProvider.getProjectId());
config.getSubscriber().setParallelPullCount(props.PUBSUB_PARALLEL_PULL_COUNT.get());
if (props.PUBSUB_OVERRIDE_SUBSCRIBER_FACTORY_CONFIG.get()) {
config.getSubscriber().setMaxDurationPerAckExtension(props.PUBSUB_MOD_ACK_DEADLINE_MAX_EXTENSION_DURATION.get().toSeconds());
}
return config;
}
//
// ~ publisher
//
@Bean
public ThreadPoolTaskScheduler pubsubPublisherThreadPool(PubSubConfiguration config) {
var scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(config.getPublisher().getExecutorThreads());
scheduler.setAcceptTasksAfterContextClose(config.getPublisher().getExecutorAcceptTasksAfterContextClose());
scheduler.setWaitForTasksToCompleteOnShutdown(config.getPublisher().getExecutorWaitForTasksToCompleteOnShutdown());
scheduler.setAwaitTerminationMillis(config.getPublisher().getExecutorAwaitTerminationMillis());
scheduler.setThreadNamePrefix("gcp-pubsub-publisher");
scheduler.setDaemon(true);
return scheduler;
}
@Bean
public ExecutorProvider publisherExecutorProvider(@Qualifier("pubsubPublisherThreadPool") ThreadPoolTaskScheduler scheduler) {
return FixedExecutorProvider.create(scheduler.getScheduledExecutor());
}
@Bean
public TransportChannelProvider publisherTransportChannelProvider(
ApplicationProperties props,
DynamicCloud cloud,
@Qualifier("emulatorTransportChannelProvider") TransportChannelProvider emulatorChannelProvider) throws IOException {
if (isLocalHost(cloud)) {
return emulatorChannelProvider;
}
return PublisherStubSettings.defaultGrpcTransportProviderBuilder()
.setEndpoint(url(cloud))
.setKeepAliveTime(Duration.ofMinutes(props.PUBSUB_KEEP_ALIVE_DURATION.get().toMinutes()))
.build();
}
@Bean
public PublisherFactory publisherFactory(
ApplicationProperties props,
DynamicCloud cloud,
GcpProjectIdProvider projectIdProvider,
@Qualifier("publisherExecutorProvider") ExecutorProvider executorProvider,
CredentialsProvider credentialsProvider,
@Qualifier("publisherTransportChannelProvider") TransportChannelProvider channelProvider) throws IOException {
var factory = new DefaultPublisherFactory(projectIdProvider);
factory.setExecutorProvider(executorProvider);
factory.setCredentialsProvider(credentialsProvider);
factory.setChannelProvider(channelProvider);
factory.setEnableMessageOrdering(props.PUBSUB_MESSAGE_ORDERING_ENABLED.get());
factory.setEndpoint(url(cloud));
if (props.PUBSUB_PRODUCER_BATCHING_ENABLED.get()) {
factory.setBatchingSettings(BatchingSettings.newBuilder()
.setElementCountThreshold((long) props.PUBSUB_PRODUCER_BATCHING_MESSAGE_COUNT.get())
.setRequestByteThreshold((long) props.PUBSUB_PRODUCER_BATCHING_BYTES_COUNT.get())
.setDelayThreshold(org.threeten.bp.Duration.ofMillis(props.PUBSUB_PRODUCER_BATCHING_DELAY.get().toMillis()))
.build());
}
return new CachingPublisherFactory(factory);
}
//
// ~ subscriber
//
@Bean
public TransportChannelProvider subscriberTransportChannelProvider(
ApplicationProperties props,
DynamicCloud cloud,
@Qualifier("emulatorTransportChannelProvider") TransportChannelProvider emulatorChannelProvider) throws IOException {
if (isLocalHost(cloud)) {
return emulatorChannelProvider;
}
return SubscriberStubSettings.defaultGrpcTransportProviderBuilder()
.setEndpoint(url(cloud))
.setKeepAliveTime(Duration.ofMinutes(props.PUBSUB_KEEP_ALIVE_DURATION.get().toMinutes()))
.build();
}
@Bean
public SubscriberFactory subscriberFactory(
DynamicCloud cloud,
GcpProjectIdProvider projectIdProvider,
CredentialsProvider credentialsProvider,
PubSubConfiguration pubSubConfiguration,
@Qualifier("subscriberTransportChannelProvider") TransportChannelProvider channelProvider) throws IOException {
var factory = new DefaultSubscriberFactory(projectIdProvider, pubSubConfiguration);
factory.setCredentialsProvider(credentialsProvider);
factory.setChannelProvider(channelProvider);
factory.setPullEndpoint(url(cloud));
return factory;
}
@Bean
public Executor pubSubAcknowledgementExecutor(PubSubConfiguration config) {
var ackExecutor = new ThreadPoolTaskExecutor();
ackExecutor.setMaxPoolSize(config.getSubscriber().getMaxAcknowledgementThreads());
ackExecutor.setThreadNamePrefix("gcp-pubsub-ack-executor");
ackExecutor.setDaemon(true);
return ackExecutor;
}
@Bean
public PubSubSubscriberTemplate pubSubSubscriberTemplate(SubscriberFactory subscriberFactory,
@Qualifier("pubSubAcknowledgementExecutor") Executor ackExecutor) {
var pubSubSubscriberTemplate = new PubSubSubscriberTemplate(subscriberFactory);
pubSubSubscriberTemplate.setAckExecutor(ackExecutor);
return pubSubSubscriberTemplate;
}
//
// ~ admin
//
@Bean
public PubSubAdmin pubSubAdmin(GcpProjectIdProvider projectIdProvider, TopicAdminClient topicAdminClient,
SubscriptionAdminClient subscriptionAdminClient) {
return new PubSubAdmin(projectIdProvider, topicAdminClient, subscriptionAdminClient);
}
@Bean
public TopicAdminClient topicAdminClient(TopicAdminSettings topicAdminSettings) {
try {
return TopicAdminClient.create(topicAdminSettings);
} catch (IOException ioe) {
throw new PubSubException("An error occurred while creating TopicAdminClient.", ioe);
}
}
@Bean
public TopicAdminSettings topicAdminSettings(CredentialsProvider credentialsProvider,
@Qualifier("publisherTransportChannelProvider") TransportChannelProvider channelProvider) {
try {
return TopicAdminSettings.newBuilder()
.setCredentialsProvider(credentialsProvider)
.setTransportChannelProvider(channelProvider)
.build();
} catch (IOException ioe) {
throw new PubSubException("An error occurred while creating TopicAdminSettings.", ioe);
}
}
@Bean
public SubscriptionAdminClient subscriptionAdminClient(
CredentialsProvider credentialsProvider,
@Qualifier("subscriberTransportChannelProvider") TransportChannelProvider channelProvider) throws IOException {
return SubscriptionAdminClient.create(
SubscriptionAdminSettings.newBuilder()
.setCredentialsProvider(credentialsProvider)
.setTransportChannelProvider(channelProvider)
.build());
}
//
// ~ emulator
//
private ManagedChannel channel;
@Bean
public ManagedChannel channel(DynamicCloud cloud) throws IOException {
this.channel = ManagedChannelBuilder.forTarget(url(cloud)).usePlaintext().build();
return channel;
}
@Bean
public TransportChannelProvider emulatorTransportChannelProvider(ManagedChannel ch) {
return FixedTransportChannelProvider.create(GrpcTransportChannel.create(ch));
}
@Override
public void destroy() throws Exception {
if (this.channel != null) {
this.channel.shutdown();
}
}
private boolean isLocalHost(DynamicCloud cloud) throws IOException {
String url = url(cloud);
return PlatformUtil.isLocalHost(url) || url.contains("localhost");
}
}