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

com.datastax.oss.pulsar.jms.PulsarJMSAdminImpl Maven / Gradle / Ivy

/*
 * Copyright DataStax, Inc.
 *
 * 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 com.datastax.oss.pulsar.jms;

import com.datastax.oss.pulsar.jms.api.JMSAdmin;
import com.datastax.oss.pulsar.jms.api.JMSDestinationMetadata;
import com.datastax.oss.pulsar.jms.selectors.SelectorSupport;
import jakarta.jms.Destination;
import jakarta.jms.IllegalStateException;
import jakarta.jms.InvalidDestinationException;
import jakarta.jms.JMSException;
import jakarta.jms.Queue;
import jakarta.jms.Topic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.apache.pulsar.client.admin.PulsarAdmin;
import org.apache.pulsar.client.admin.PulsarAdminException;
import org.apache.pulsar.client.admin.Topics;
import org.apache.pulsar.client.api.MessageId;
import org.apache.pulsar.client.api.PulsarClient;
import org.apache.pulsar.common.partition.PartitionedTopicMetadata;
import org.apache.pulsar.common.policies.data.ConsumerStats;
import org.apache.pulsar.common.policies.data.PartitionedTopicStats;
import org.apache.pulsar.common.policies.data.PublisherStats;
import org.apache.pulsar.common.policies.data.SubscriptionStats;
import org.apache.pulsar.common.policies.data.TopicStats;

@Slf4j
class PulsarJMSAdminImpl implements JMSAdmin {
  private final PulsarConnectionFactory factory;

  PulsarJMSAdminImpl(PulsarConnectionFactory factory) {
    this.factory = factory;
  }

  @Override
  public Queue getQueue(String queue) throws JMSException {
    return new PulsarQueue(queue);
  }

  @Override
  public Topic getTopic(String topic) throws JMSException {
    return new PulsarTopic(topic);
  }

  @Override
  public JMSDestinationMetadata describe(Destination dest) throws JMSException {
    PulsarDestination destination = PulsarConnectionFactory.toPulsarDestination(dest);

    if (destination.isMultiTopic()) {

      List subDestinationsMetadata = new ArrayList<>();
      List destinations = destination.getDestinations();
      for (PulsarDestination sub : destinations) {
        JMSDestinationMetadata subDestinationMetadata = describe(sub);
        subDestinationsMetadata.add(subDestinationMetadata);
      }
      return new JMSDestinationMetadata.VirtualDestinationMetadata(
          destination.getName(),
          destination.isQueue(),
          destination.isMultiTopic(),
          destination.isRegExp(),
          subDestinationsMetadata);
    } else if (destination.isRegExp()) {

      List subDestinationsMetadata = new ArrayList<>();
      PulsarClient pulsarClient = factory.ensureClient();
      String topicName = factory.getPulsarTopicName(destination);
      List topics =
          TopicDiscoveryUtils.discoverTopicsByPattern(topicName, pulsarClient, 10000);
      String customSubscription = destination.extractSubscriptionName();
      for (String topic : topics) {
        if (customSubscription != null) {
          topic = topic + ":" + customSubscription;
        }
        PulsarDestination sub = destination.createSameType(topic);
        JMSDestinationMetadata subDestinationMetadata = describe(sub);
        subDestinationsMetadata.add(subDestinationMetadata);
      }
      return new JMSDestinationMetadata.VirtualDestinationMetadata(
          destination.getName(),
          destination.isQueue(),
          destination.isMultiTopic(),
          destination.isRegExp(),
          subDestinationsMetadata);
    } else {
      return describeDestination(destination);
    }
  }

  private JMSDestinationMetadata describeDestination(PulsarDestination destination)
      throws JMSException {
    PulsarAdmin pulsarAdmin = factory.ensurePulsarAdmin();
    String pulsarTopic = factory.getPulsarTopicName(destination);
    String queueSubscription;
    if (destination.isQueue()) {
      queueSubscription = factory.getQueueSubscriptionName(destination);
    } else {
      queueSubscription = null;
    }
    boolean exists = false;
    Map subscriptions = Collections.emptyMap();
    List publishers = Collections.emptyList();
    PartitionedTopicMetadata partitionedTopicMetadata;
    try {
      partitionedTopicMetadata = pulsarAdmin.topics().getPartitionedTopicMetadata(pulsarTopic);
      exists = true;
    } catch (PulsarAdminException.NotFoundException notFound) {
      partitionedTopicMetadata = new PartitionedTopicMetadata(0);
    } catch (PulsarAdminException err) {
      throw Utils.handleException(err);
    }
    int partitions = partitionedTopicMetadata.partitions;
    final Map partitionsStats;

    if (exists) {
      try {
        if (partitionedTopicMetadata.partitions > 0) {
          PartitionedTopicStats partitionedStats =
              pulsarAdmin.topics().getPartitionedStats(pulsarTopic, true);
          subscriptions = partitionedStats.getSubscriptions();
          publishers = partitionedStats.getPublishers();
          partitionsStats = partitionedStats.getPartitions();
        } else {
          TopicStats stats = pulsarAdmin.topics().getStats(pulsarTopic);
          subscriptions = stats.getSubscriptions();
          publishers = stats.getPublishers();
          partitionsStats = Collections.emptyMap();
        }
        if (subscriptions == null) {
          subscriptions = Collections.emptyMap();
        }
        if (publishers == null) {
          publishers = Collections.emptyList();
        }
      } catch (PulsarAdminException err) {
        throw Utils.handleException(err);
      }
    } else {
      partitionsStats = Collections.emptyMap();
    }

    List subscriptionMetadataList = new ArrayList<>();

    boolean queueSubscriptionExists = false;
    if (destination.isQueue()) {
      queueSubscriptionExists = subscriptions.containsKey(queueSubscription);
    }
    subscriptions.forEach(
        (subscriptionName, sub) -> {
          if (destination.isQueue() && !subscriptionName.equals(queueSubscription)) {
            // this is a JMS Queue, skip other subscriptions
            return;
          }

          JMSDestinationMetadata.SubscriptionMetadata md =
              new JMSDestinationMetadata.SubscriptionMetadata(subscriptionName);
          subscriptionMetadataList.add(md);
          Map subscriptionProperties =
              sub.getSubscriptionProperties() != null
                  ? sub.getSubscriptionProperties()
                  : Collections.emptyMap();
          md.setSubscriptionProperties(subscriptionProperties);

          String jmsFiltering = subscriptionProperties.getOrDefault("jms.filtering", "false");
          if ("true".equals(jmsFiltering)) {
            md.setEnableFilters(true);
            String jmsSelector = subscriptionProperties.getOrDefault("jms.selector", "");
            md.setSelector(jmsSelector);
          }
          List consumers = new ArrayList<>();
          md.setConsumers(consumers);
          if (partitions > 0) {
            partitionsStats.forEach(
                (partitionName, topicStats) -> {
                  SubscriptionStats subscriptionStats =
                      topicStats.getSubscriptions().get(subscriptionName);
                  if (subscriptionStats != null) {
                    subscriptionStats
                        .getConsumers()
                        .forEach(
                            c -> {
                              JMSDestinationMetadata.ConsumerMetadata cmd =
                                  buildConsumerMetadata(partitionName, subscriptionName, c);
                              consumers.add(cmd);
                            });
                  }
                });
          } else {
            sub.getConsumers()
                .forEach(
                    c -> {
                      JMSDestinationMetadata.ConsumerMetadata cmd =
                          buildConsumerMetadata(pulsarTopic, subscriptionName, c);
                      consumers.add(cmd);
                    });
          }
        });

    List producerMetadataList = new ArrayList<>();
    if (partitions > 0) {
      // for partitioned topic we list all the producers connected to each partition
      partitionsStats.forEach(
          (partition, stats) -> {
            stats
                .getPublishers()
                .forEach(
                    p -> {
                      JMSDestinationMetadata.ProducerMetadata cmd =
                          buildProducerMetadata(partition, p);
                      producerMetadataList.add(cmd);
                    });
          });
    } else {
      publishers.forEach(
          p -> {
            JMSDestinationMetadata.ProducerMetadata cmd = buildProducerMetadata(pulsarTopic, p);
            producerMetadataList.add(cmd);
          });
    }
    if (destination.isQueue()) {
      JMSDestinationMetadata.SubscriptionMetadata subscriptionMetadata =
          subscriptionMetadataList.isEmpty() ? null : subscriptionMetadataList.get(0);
      return new JMSDestinationMetadata.QueueMetadata(
          destination.getName(),
          exists,
          pulsarTopic,
          producerMetadataList,
          partitions,
          queueSubscription,
          queueSubscriptionExists,
          subscriptionMetadata);
    } else {
      return new JMSDestinationMetadata.TopicMetadata(
          destination.getName(),
          exists,
          pulsarTopic,
          producerMetadataList,
          partitions,
          subscriptionMetadataList);
    }
  }

  private static JMSDestinationMetadata.ConsumerMetadata buildConsumerMetadata(
      String pulsarTopic, String subscriptionName, ConsumerStats c) {
    JMSDestinationMetadata.ConsumerMetadata cmd =
        new JMSDestinationMetadata.ConsumerMetadata(c.getConsumerName());
    cmd.setSubscriptionName(subscriptionName);
    cmd.setPulsarTopic(pulsarTopic);
    cmd.setAddress(c.getAddress());
    cmd.setClientVersion(c.getClientVersion());
    Map metadata =
        c.getMetadata() != null ? c.getMetadata() : Collections.emptyMap();
    cmd.setMetadata(metadata);
    cmd.setAcknowledgeMode(metadata.getOrDefault("jms.acknowledgeMode", "?"));
    String jmsConsumerFiltering = metadata.getOrDefault("jms.filtering", "false");
    if ("true".equals(jmsConsumerFiltering)) {
      cmd.setEnableFilters(true);
      String jmsSelector = metadata.getOrDefault("jms.selector", "");
      cmd.setSelector(jmsSelector);
    }
    cmd.setEnablePriority(metadata.getOrDefault("jms.priority", "disabled").equals("enabled"));
    return cmd;
  }

  private static JMSDestinationMetadata.ProducerMetadata buildProducerMetadata(
      String pulsarTopic, PublisherStats p) {
    JMSDestinationMetadata.ProducerMetadata cmd =
        new JMSDestinationMetadata.ProducerMetadata(p.getProducerName());
    cmd.setPulsarTopic(pulsarTopic);
    cmd.setAddress(p.getAddress());
    cmd.setClientVersion(p.getClientVersion());
    Map metadata =
        p.getMetadata() != null ? p.getMetadata() : Collections.emptyMap();
    cmd.setMetadata(metadata);
    cmd.setEnablePriority(metadata.getOrDefault("jms.priority", "disabled").equals("enabled"));
    cmd.setTransacted(metadata.getOrDefault("jms.transactions", "disabled").equals("enabled"));
    cmd.setPriorityMapping(metadata.getOrDefault("jms.priorityMapping", ""));
    return cmd;
  }

  void checkDestination(
      Destination destination, Function condition, String message)
      throws JMSException {
    if (!condition.apply(destination)) {
      throw new InvalidDestinationException(message);
    }
  }

  void checkArgument(Supplier condition, String message) throws JMSException {
    if (!condition.get()) {
      throw new IllegalStateException(message);
    }
  }

  void validateSelector(boolean enableFilters, String selector) throws JMSException {
    if (enableFilters) {
      SelectorSupport.build(selector, true);
    }
  }

  @Override
  public void createSubscription(
      Topic destination,
      String subscriptionName,
      boolean enableFilters,
      String selector,
      boolean fromBeginning)
      throws JMSException {
    try {
      PulsarDestination dest = PulsarConnectionFactory.toPulsarDestination(destination);
      validateSelector(enableFilters, selector);
      Map properties = new HashMap<>();
      if (enableFilters) {
        properties.put("jms.filtering", "true");
        properties.put("jms.selector", selector);
      }
      String topicName = factory.getPulsarTopicName(dest);
      Topics topics = factory.ensurePulsarAdmin().topics();
      topics.createSubscription(
          topicName,
          subscriptionName,
          fromBeginning ? MessageId.earliest : MessageId.latest,
          false,
          properties);
    } catch (PulsarAdminException error) {
      throw Utils.handleException(error);
    }
  }

  @Override
  public void createQueue(Queue destination, int partitions, boolean enableFilters, String selector)
      throws JMSException {
    checkArgument(() -> partitions >= 0, "Invalid number of partitions " + partitions);
    validateSelector(enableFilters, selector);
    try {
      PulsarDestination dest = PulsarConnectionFactory.toPulsarDestination(destination);
      checkDestination(
          destination, d -> !dest.isVirtualDestination(), "Cannot create a VirtualDestination");

      String topicName = factory.getPulsarTopicName(dest);
      Topics topics = factory.ensurePulsarAdmin().topics();
      boolean exists = false;
      try {
        PartitionedTopicMetadata partitionedTopicMetadata =
            topics.getPartitionedTopicMetadata(topicName);
        checkDestination(
            destination,
            d -> partitionedTopicMetadata.partitions == partitions,
            "Destination exists and it has a different number of partitions "
                + partitionedTopicMetadata.partitions
                + " is different from "
                + partitions);
        exists = true;
      } catch (PulsarAdminException.NotFoundException notFound) {
        // ok
      }
      String subscriptionName = factory.getQueueSubscriptionName(dest);
      if (!exists) {
        if (partitions > 0) {
          topics.createPartitionedTopic(topicName, partitions);
        } else {
          topics.createNonPartitionedTopic(topicName);
        }
      }
      Map properties = new HashMap<>();
      if (enableFilters) {
        properties.put("jms.filtering", "true");
        properties.put("jms.selector", selector);
      }
      try {
        topics.createSubscription(
            topicName, subscriptionName, MessageId.earliest, false, properties);
      } catch (PulsarAdminException.ConflictException alreadyExists) {
        log.debug("Already exists", alreadyExists);
        throw new InvalidDestinationException(
            "Subscription " + subscriptionName + " already exists on Pulsar Topic " + topicName);
      }
    } catch (PulsarAdminException error) {
      throw Utils.handleException(error);
    }
  }

  @Override
  public void createTopic(Topic destination, int partitions) throws JMSException {
    checkArgument(() -> partitions >= 0, "Invalid number of partitions " + partitions);
    try {
      PulsarDestination dest = PulsarConnectionFactory.toPulsarDestination(destination);
      checkDestination(
          destination, d -> !dest.isVirtualDestination(), "Cannot create a VirtualDestination");

      String topicName = factory.getPulsarTopicName(dest);
      Topics topics = factory.ensurePulsarAdmin().topics();
      try {
        PartitionedTopicMetadata partitionedTopicMetadata =
            topics.getPartitionedTopicMetadata(topicName);
        checkDestination(
            destination,
            d -> partitionedTopicMetadata.partitions != partitions,
            "Destination exists and it has a different number of partitions "
                + partitionedTopicMetadata.partitions
                + " is different from "
                + partitions);
      } catch (PulsarAdminException.NotFoundException notFound) {
        // ok
      }
      try {
        if (partitions > 0) {
          topics.createPartitionedTopic(topicName, partitions);
        } else {
          topics.createNonPartitionedTopic(topicName);
        }
      } catch (PulsarAdminException.ConflictException exists) {
        throw new InvalidDestinationException("Topic " + topicName + " already exists");
      }
    } catch (PulsarAdminException error) {
      throw Utils.handleException(error);
    }
  }

  @Override
  public void setQueueSubscriptionSelector(
      Queue destination, boolean enableFilters, String selector) throws JMSException {
    try {
      PulsarDestination dest = PulsarConnectionFactory.toPulsarDestination(destination);
      String topicName = factory.getPulsarTopicName(dest);
      String subscriptionName = factory.getQueueSubscriptionName(dest);
      doUpdateSubscriptionSelector(enableFilters, selector, topicName, subscriptionName);
    } catch (PulsarAdminException error) {
      throw Utils.handleException(error);
    }
  }

  private void doUpdateSubscriptionSelector(
      boolean enableFilters, String selector, String topicName, String subscriptionName)
      throws JMSException, PulsarAdminException {
    validateSelector(enableFilters, selector);
    Topics topics = factory.ensurePulsarAdmin().topics();
    Map currentProperties = new HashMap<>();
    try {
      currentProperties = topics.getSubscriptionProperties(topicName, subscriptionName);
    } catch (PulsarAdminException.NotFoundException notFoundException) {
    }
    currentProperties.put("jms.filtering", enableFilters + "");
    if (enableFilters) {
      currentProperties.put("jms.selector", selector);
    } else {
      currentProperties.remove("jms.selector");
    }
    topics.updateSubscriptionProperties(topicName, subscriptionName, currentProperties);
  }

  @Override
  public void setSubscriptionSelector(
      Topic destination, String subscriptionName, boolean enableFilters, String selector)
      throws JMSException {
    try {
      PulsarDestination dest = PulsarConnectionFactory.toPulsarDestination(destination);
      String topicName = factory.getPulsarTopicName(dest);
      doUpdateSubscriptionSelector(enableFilters, selector, topicName, subscriptionName);
    } catch (PulsarAdminException error) {
      throw Utils.handleException(error);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy