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

com.rabbitmq.stream.impl.SuperStreamConsumer 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.Consumer;
import com.rabbitmq.stream.Message;
import com.rabbitmq.stream.MessageHandler;
import com.rabbitmq.stream.impl.StreamConsumerBuilder.TrackingConfiguration;
import com.rabbitmq.stream.impl.Utils.CompositeConsumerUpdateListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class SuperStreamConsumer implements Consumer {

  private static final Logger LOGGER = LoggerFactory.getLogger(SuperStreamConsumer.class);
  private final String superStream;
  private final Map consumers = new ConcurrentHashMap<>();

  SuperStreamConsumer(
      StreamConsumerBuilder builder,
      String superStream,
      StreamEnvironment environment,
      TrackingConfiguration trackingConfiguration) {
    this.superStream = superStream;
    List partitions =
        environment.locatorOperation(
            namedFunction(
                c -> c.partitions(superStream),
                "Partition lookup for super stream '%s'",
                superStream));

    // for manual offset tracking strategy only
    ConsumerState[] states = new ConsumerState[partitions.size()];
    Map partitionToStates = new HashMap<>(partitions.size());
    for (int i = 0; i < partitions.size(); i++) {
      ConsumerState state = new ConsumerState();
      states[i] = state;
      partitionToStates.put(partitions.get(i), state);
    }
    // end of manual offset tracking strategy

    for (String partition : partitions) {
      ConsumerState state = partitionToStates.get(partition);
      MessageHandler messageHandler;
      if (trackingConfiguration.enabled() && trackingConfiguration.manual()) {
        messageHandler =
            new ManualOffsetTrackingMessageHandler(builder.messageHandler(), states, state);
      } else {
        messageHandler = builder.messageHandler();
      }
      StreamConsumerBuilder subConsumerBuilder = builder.duplicate();

      // to ease testing
      // we need to duplicate the composite consumer update listener,
      // otherwise a unique instance would get the listeners of all the sub-consumers
      if (subConsumerBuilder.consumerUpdateListener() instanceof CompositeConsumerUpdateListener) {
        subConsumerBuilder.consumerUpdateListener(
            ((CompositeConsumerUpdateListener) subConsumerBuilder.consumerUpdateListener())
                .duplicate());
      }

      if (trackingConfiguration.enabled() && trackingConfiguration.auto()) {
        subConsumerBuilder =
            (StreamConsumerBuilder)
                subConsumerBuilder
                    .autoTrackingStrategy()
                    .messageCountBeforeStorage(
                        trackingConfiguration.autoMessageCountBeforeStorage() / partitions.size())
                    .builder();
      }

      StreamConsumer consumer =
          (StreamConsumer)
              subConsumerBuilder
                  .lazyInit(true)
                  .superStream(null)
                  .messageHandler(messageHandler)
                  .stream(partition)
                  .build();
      consumers.put(partition, consumer);
      state.consumer = consumer;
      LOGGER.debug("Created consumer on stream '{}' for super stream '{}'", partition, superStream);
    }

    consumers.values().forEach(c -> ((StreamConsumer) c).start());
  }

  private static final class ConsumerState {

    private volatile long offset = 0;
    private volatile StreamConsumer consumer;
  }

  private static final class ManualOffsetTrackingMessageHandler implements MessageHandler {

    private final MessageHandler delegate;
    private final ConsumerState[] consumerStates;
    private final ConsumerState consumerState;

    private ManualOffsetTrackingMessageHandler(
        MessageHandler delegate, ConsumerState[] consumerStates, ConsumerState consumerState) {
      this.delegate = delegate;
      this.consumerStates = consumerStates;
      this.consumerState = consumerState;
    }

    @Override
    public void handle(Context context, Message message) {
      Context ctx =
          new Context() {
            @Override
            public long offset() {
              return context.offset();
            }

            @Override
            public long timestamp() {
              return context.timestamp();
            }

            @Override
            public long committedChunkId() {
              return context.committedChunkId();
            }

            @Override
            public void storeOffset() {
              for (ConsumerState state : consumerStates) {
                if (ManualOffsetTrackingMessageHandler.this.consumerState == state) {
                  maybeStoreOffset(state, () -> context.storeOffset());
                } else if (state.offset != 0) {
                  maybeStoreOffset(state, () -> state.consumer.store(state.offset));
                }
              }
            }

            @Override
            public void processed() {
              context.processed();
            }

            private void maybeStoreOffset(ConsumerState state, Runnable storeAction) {
              if (state.consumer.isSac() && !state.consumer.sacActive()) {
                // do nothing
              } else {
                storeAction.run();
              }
            }

            @Override
            public String stream() {
              return context.stream();
            }

            @Override
            public Consumer consumer() {
              return context.consumer();
            }
          };
      this.delegate.handle(ctx, message);
      consumerState.offset = context.offset();
    }
  }

  @Override
  public void store(long offset) {
    throw new UnsupportedOperationException(
        "Consumer#store(long) does not work for super streams, use MessageHandler.Context#storeOffset() instead");
  }

  Consumer consumer(String partition) {
    return this.consumers.get(partition);
  }

  @Override
  public long storedOffset() {
    throw new UnsupportedOperationException(
        "Consumer#storedOffset() does not work for super streams");
  }

  @Override
  public void close() {
    for (Entry entry : consumers.entrySet()) {
      LOGGER.debug(
          "Closing consumer for partition '{}' of super stream {}",
          entry.getKey(),
          this.superStream);
      try {
        entry.getValue().close();
      } catch (Exception e) {
        LOGGER.info(
            "Error while closing consumer for partition {} of super stream {}: {}",
            entry.getKey(),
            this.superStream,
            e.getMessage());
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy