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

io.github.microcks.minion.async.consumer.GooglePubSubMessageConsumptionTask Maven / Gradle / Ivy

The newest version!
/*
 * Copyright The Microcks Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.microcks.minion.async.consumer;

import com.google.protobuf.Duration;
import com.google.pubsub.v1.*;
import io.github.microcks.minion.async.AsyncTestSpecification;

import com.google.api.gax.rpc.NotFoundException;
import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.core.NoCredentialsProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.pubsub.v1.MessageReceiver;
import com.google.cloud.pubsub.v1.Subscriber;
import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.cloud.pubsub.v1.SubscriptionAdminSettings;
import org.jboss.logging.Logger;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * An implementation of MessageConsumptionTask that consumes a queue on Google Cloud PubSub. Endpoint URL
 * should be specified using the following form:
 * googlepubsub://{projectId}/{topic}[?option1=value1&option2=value2]
 * @author laurent
 */
public class GooglePubSubMessageConsumptionTask implements MessageConsumptionTask {

   /** Get a JBoss logging logger. */
   private final Logger logger = Logger.getLogger(getClass());

   /** The string for Regular Expression that helps validating acceptable endpoints. */
   public static final String ENDPOINT_PATTERN_STRING = "googlepubsub://(?[a-zA-Z0-9-_]+)/(?[a-zA-Z0-9-_\\.]+)(\\?(?.+))?";
   /** The Pattern for matching groups within the endpoint regular expression. */
   public static final Pattern ENDPOINT_PATTERN = Pattern.compile(ENDPOINT_PATTERN_STRING);

   private static final String CLOUD_OAUTH_SCOPE = "https://www.googleapis.com/auth/cloud-platform";

   private static final String SUBSCRIPTION_PREFIX = "-microcks-test";

   private AsyncTestSpecification specification;

   protected Map optionsMap;

   private CredentialsProvider credentialsProvider;

   private SubscriptionName subscriptionName;

   private Subscriber subscriber;

   /**
    * Creates a new consumption task from an Async test specification.
    * @param testSpecification The specification holding endpointURL and timeout.
    */
   public GooglePubSubMessageConsumptionTask(AsyncTestSpecification testSpecification) {
      this.specification = testSpecification;
   }

   /**
    * Convenient static method for checking if this implementation will accept endpoint.
    * @param endpointUrl The endpoint URL to validate
    * @return True if endpointUrl can be used for connecting and consuming on endpoint
    */
   public static boolean acceptEndpoint(String endpointUrl) {
      return endpointUrl != null && endpointUrl.matches(ENDPOINT_PATTERN_STRING);
   }

   @Override
   public List call() throws Exception {
      // As initialization can be long (circa 1 sec), we have to remove this time from wait time.
      long start = System.currentTimeMillis();
      if (subscriber == null) {
         initializePubSubSubscriber();
      }
      List messages = new ArrayList<>();

      // Subscribe to PubSub.
      MessageReceiver receiver = (message, consumer) -> {
         logger.info("Received a new PubSub Message: " + message.getData().toStringUtf8());
         // Build a ConsumedMessage from PubSub message.
         ConsumedMessage consumedMessage = new ConsumedMessage();
         consumedMessage.setReceivedAt(System.currentTimeMillis());
         consumedMessage.setPayload(message.getData().toByteArray());
         messages.add(consumedMessage);

         consumer.ack();
      };

      // Create a new subscriber for subscription.
      subscriber = Subscriber
            .newBuilder(ProjectSubscriptionName.of(subscriptionName.getProject(), subscriptionName.getSubscription()),
                  receiver)
            .setCredentialsProvider(credentialsProvider).build();
      subscriber.startAsync().awaitRunning();

      // Wait and stop async receiver.
      Thread.sleep(specification.getTimeoutMS() - (System.currentTimeMillis() - start));
      subscriber.stopAsync();

      return messages;
   }

   @Override
   public void close() throws IOException {
      if (subscriber != null && subscriber.isRunning()) {
         subscriber.stopAsync();
      }
   }

   /** Initialize Google Pub Sub consumer from test properties. */
   private void initializePubSubSubscriber() throws Exception {
      Matcher matcher = ENDPOINT_PATTERN.matcher(specification.getEndpointUrl().trim());
      // Call matcher.find() to be able to use named expressions.
      matcher.find();

      String projectId = matcher.group("projectId");
      String topic = matcher.group("topic");
      String options = matcher.group("options");

      // Parse options if specified.
      if (options != null && !options.isBlank()) {
         optionsMap = ConsumptionTaskCommons.initializeOptionsMap(options);
      }

      // Build credential provider from secret token.
      if (specification.getSecret() != null && specification.getSecret().getToken() != null) {
         byte[] decode = Base64.getDecoder().decode(specification.getSecret().getToken());
         ByteArrayInputStream is = new ByteArrayInputStream(decode);
         credentialsProvider = FixedCredentialsProvider
               .create(GoogleCredentials.fromStream(is).createScoped(CLOUD_OAUTH_SCOPE));
      } else {
         credentialsProvider = NoCredentialsProvider.create();
      }

      // Ensure connection is possible and subscription exists.
      TopicName topicName = TopicName.of(projectId, topic);
      subscriptionName = SubscriptionName.of(projectId, topic + SUBSCRIPTION_PREFIX);

      SubscriptionAdminSettings subscriptionAdminSettings = SubscriptionAdminSettings.newBuilder()
            .setCredentialsProvider(credentialsProvider).build();
      SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create(subscriptionAdminSettings);

      try {
         subscriptionAdminClient.getSubscription(subscriptionName);
      } catch (NotFoundException nfe) {
         logger.infof("Subscription {%s} does not exist yet, creating it", subscriptionName);

         // Customize subscription to avoid retention and let google auto-cleanup it.
         // Put the durations to the minimum values accepted by Google cloud.
         Subscription subscriptionRequest = Subscription.newBuilder().setName(subscriptionName.toString())
               .setTopic(topicName.toString()).setPushConfig(PushConfig.getDefaultInstance()).setAckDeadlineSeconds(10)
               .setRetainAckedMessages(false).setMessageRetentionDuration(Duration.newBuilder().setSeconds(600).build())
               .setExpirationPolicy(
                     ExpirationPolicy.newBuilder().setTtl(Duration.newBuilder().setSeconds(24 * 3600L)).build())
               .setEnableMessageOrdering(false).setEnableExactlyOnceDelivery(false).build();
         subscriptionAdminClient.createSubscription(subscriptionRequest);
      } finally {
         subscriptionAdminClient.close();
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy