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

com.rabbitmq.stream.impl.SuperStreamProducer Maven / Gradle / Ivy

Go to download

The RabbitMQ Stream Java client library allows Java applications to interface with RabbitMQ Stream.

The newest version!
// Copyright (c) 2021-2023 Broadcom. All Rights Reserved.
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
//
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
// Mozilla Public License 2.0 ("MPL"), and the Apache License version 2 ("ASL").
// For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].
package com.rabbitmq.stream.impl;

import static com.rabbitmq.stream.impl.Utils.namedFunction;

import com.rabbitmq.stream.*;
import com.rabbitmq.stream.RoutingStrategy.Metadata;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class SuperStreamProducer implements Producer {

  private static final Logger LOGGER = LoggerFactory.getLogger(SuperStreamProducer.class);

  private final RoutingStrategy routingStrategy;
  private final Codec codec;
  private final String superStream;
  private final Map producers = new ConcurrentHashMap<>();
  private final StreamProducerBuilder producerBuilder;
  private final StreamEnvironment environment;
  private final String name;
  private final Metadata superStreamMetadata;
  private final AtomicBoolean closed = new AtomicBoolean(false);

  /**
   * passthrough, except when observation collector is not no-op and a message must be sent to
   * several streams. In this it creates a copy of the message with distinct message annotations for
   * each message, so that the collector can populate these messages annotations without collision
   */
  private final MessageInterceptor messageInterceptor;

  SuperStreamProducer(
      StreamProducerBuilder producerBuilder,
      String name,
      String superStream,
      RoutingStrategy routingStrategy,
      StreamEnvironment streamEnvironment) {
    this.routingStrategy = routingStrategy;
    this.codec = streamEnvironment.codec();
    this.name = name;
    this.superStream = superStream;
    this.environment = streamEnvironment;
    this.superStreamMetadata = new DefaultSuperStreamMetadata(this.superStream, this.environment);
    this.producerBuilder = producerBuilder.duplicate();
    this.producerBuilder.stream(null);
    this.producerBuilder.resetRouting();
    this.messageInterceptor =
        environment.observationCollector().isNoop()
            ? (i, msg) -> msg
            : (i, msg) -> i == 0 ? msg : msg.copy();
  }

  @Override
  public MessageBuilder messageBuilder() {
    return codec.messageBuilder();
  }

  @Override
  public long getLastPublishingId() {
    if (this.name != null && !this.name.isEmpty()) {
      List streams =
          this.environment.locatorOperation(
              namedFunction(
                  c -> c.partitions(superStream),
                  "Partition lookup for super stream '%s'",
                  this.superStream));
      long publishingId = 0;
      boolean first = true;
      for (String partition : streams) {
        long pubId =
            this.environment.locatorOperation(
                namedFunction(
                    c -> c.queryPublisherSequence(this.name, partition),
                    "Publisher sequence query for on partition '%s' of super stream '%s', publisher name '%s'",
                    partition,
                    this.superStream,
                    this.name));
        if (first) {
          publishingId = pubId;
          first = false;
        } else {
          if (Long.compareUnsigned(publishingId, pubId) > 0) {
            publishingId = pubId;
          }
        }
      }
      return publishingId;
    } else {
      throw new IllegalStateException("The producer has no name");
    }
  }

  @Override
  public void send(Message message, ConfirmationHandler confirmationHandler) {
    if (canSend()) {
      List streams = this.routingStrategy.route(message, superStreamMetadata);
      if (streams.isEmpty()) {
        confirmationHandler.handle(
            new ConfirmationStatus(message, false, Constants.CODE_NO_ROUTE_FOUND));
      } else if (streams.size() == 1) {
        producer(streams.get(0)).send(message, confirmationHandler);
      } else {
        for (int i = 0; i < streams.size(); i++) {
          Producer producer = producer(streams.get(i));
          producer(streams.get(i)).send(messageInterceptor.apply(i, message), confirmationHandler);
        }
      }
    } else {
      confirmationHandler.handle(
          new ConfirmationStatus(message, false, Constants.CODE_PRODUCER_CLOSED));
    }
  }

  private Producer producer(String stream) {
    return producers.computeIfAbsent(
        stream,
        stream1 -> {
          Producer p = producerBuilder.duplicate().superStream(null).stream(stream1).build();
          return p;
        });
  }

  private boolean canSend() {
    return !this.closed.get();
  }

  @Override
  public void close() {
    if (this.closed.compareAndSet(false, true)) {
      for (Entry entry : producers.entrySet()) {
        try {
          entry.getValue().close();
        } catch (Exception e) {
          LOGGER.info(
              "Error while closing producer for partition {} of super stream {}: {}",
              entry.getKey(),
              this.superStream,
              e.getMessage());
        }
      }
    }
  }

  private static final class DefaultSuperStreamMetadata implements Metadata {

    private final String superStream;
    private final StreamEnvironment environment;
    private final List partitions;
    private final Map> routes = new ConcurrentHashMap<>();

    private DefaultSuperStreamMetadata(String superStream, StreamEnvironment environment) {
      this.superStream = superStream;
      this.environment = environment;
      List ps =
          environment.locatorOperation(
              namedFunction(
                  c -> c.partitions(superStream),
                  "Partition lookup for super stream '%s'",
                  superStream));
      if (ps.isEmpty()) {
        throw new IllegalArgumentException("Super stream '" + superStream + "' has no partition");
      }
      this.partitions = new CopyOnWriteArrayList<>(ps);
    }

    @Override
    public List partitions() {
      return partitions;
    }

    @Override
    public List route(String routingKey) {
      return routes.computeIfAbsent(
          routingKey,
          routingKey1 ->
              environment.locatorOperation(
                  namedFunction(
                      c -> c.route(routingKey1, superStream),
                      "Route lookup on super stream '%s' for key '%s'",
                      this.superStream,
                      routingKey1)));
    }
  }

  @FunctionalInterface
  private interface MessageInterceptor {

    Message apply(int partitionIndex, Message message);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy