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

com.rabbitmq.stream.impl.StreamConsumerBuilder 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) 2020-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.SUBSCRIPTION_PROPERTY_FILTER_PREFIX;
import static com.rabbitmq.stream.impl.Utils.SUBSCRIPTION_PROPERTY_MATCH_UNFILTERED;

import com.rabbitmq.stream.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

class StreamConsumerBuilder implements ConsumerBuilder {

  private static final int NAME_MAX_SIZE = Client.MAX_REFERENCE_SIZE; // server-side limitation
  private static final TrackingConfiguration DISABLED_TRACKING_CONFIGURATION =
      new TrackingConfiguration(false, false, -1, Duration.ZERO, Duration.ZERO);
  private final StreamEnvironment environment;
  private final Map subscriptionProperties = new ConcurrentHashMap<>();
  private String stream, superStream;
  private OffsetSpecification offsetSpecification = null;
  private MessageHandler messageHandler;
  private String name;
  private DefaultAutoTrackingStrategy autoTrackingStrategy;
  private DefaultManualTrackingStrategy manualTrackingStrategy;
  private boolean noTrackingStrategy = false;
  private boolean lazyInit = false;
  private SubscriptionListener subscriptionListener = subscriptionContext -> {};
  private final DefaultFlowConfiguration flowConfiguration = new DefaultFlowConfiguration(this);
  private ConsumerUpdateListener consumerUpdateListener;
  private DefaultFilterConfiguration filterConfiguration;

  public StreamConsumerBuilder(StreamEnvironment environment) {
    this.environment = environment;
  }

  @Override
  public ConsumerBuilder stream(String stream) {
    this.stream = stream;
    return this;
  }

  @Override
  public ConsumerBuilder superStream(String superStream) {
    this.superStream = superStream;
    return this;
  }

  @Override
  public ConsumerBuilder offset(OffsetSpecification offsetSpecification) {
    this.offsetSpecification = offsetSpecification;
    return this;
  }

  @Override
  public ConsumerBuilder messageHandler(MessageHandler messageHandler) {
    this.messageHandler = messageHandler;
    return this;
  }

  MessageHandler messageHandler() {
    return this.messageHandler;
  }

  @Override
  public ConsumerBuilder name(String name) {
    if (name == null || name.length() >= NAME_MAX_SIZE) {
      throw new IllegalArgumentException(
          "The consumer name must be non-null and less than 256 characters");
    }
    this.name = name;
    return this;
  }

  @Override
  public ConsumerBuilder singleActiveConsumer() {
    this.subscriptionProperties.put(Utils.SUBSCRIPTION_PROPERTY_SAC, "true");
    return this;
  }

  @Override
  public ConsumerBuilder consumerUpdateListener(ConsumerUpdateListener consumerUpdateListener) {
    this.consumerUpdateListener = consumerUpdateListener;
    return this;
  }

  @Override
  public ConsumerBuilder subscriptionListener(SubscriptionListener subscriptionListener) {
    if (subscriptionListener == null) {
      throw new IllegalArgumentException("The subscription listener cannot be null");
    }
    this.subscriptionListener = subscriptionListener;
    return this;
  }

  @Override
  public ManualTrackingStrategy manualTrackingStrategy() {
    this.manualTrackingStrategy = new DefaultManualTrackingStrategy(this);
    this.autoTrackingStrategy = null;
    this.noTrackingStrategy = false;
    return this.manualTrackingStrategy;
  }

  @Override
  public AutoTrackingStrategy autoTrackingStrategy() {
    this.autoTrackingStrategy = new DefaultAutoTrackingStrategy(this);
    this.manualTrackingStrategy = null;
    this.noTrackingStrategy = false;
    return this.autoTrackingStrategy;
  }

  @Override
  public ConsumerBuilder noTrackingStrategy() {
    this.noTrackingStrategy = true;
    this.autoTrackingStrategy = null;
    this.manualTrackingStrategy = null;
    return this;
  }

  @Override
  public FlowConfiguration flow() {
    return this.flowConfiguration;
  }

  StreamConsumerBuilder lazyInit(boolean lazyInit) {
    this.lazyInit = lazyInit;
    return this;
  }

  @Override
  public FilterConfiguration filter() {
    if (this.filterConfiguration == null) {
      this.filterConfiguration = new DefaultFilterConfiguration(this);
    }
    return this.filterConfiguration;
  }

  @Override
  public Consumer build() {
    if (this.stream == null && this.superStream == null) {
      throw new IllegalArgumentException("A stream must be specified");
    }
    if (this.stream != null && this.superStream != null) {
      throw new IllegalArgumentException("Stream and superStream cannot be set at the same time");
    }
    if (this.messageHandler == null) {
      throw new IllegalArgumentException("A message handler must be set");
    }
    if (this.name == null
        && !this.noTrackingStrategy
        && (this.autoTrackingStrategy != null || this.manualTrackingStrategy != null)) {
      throw new IllegalArgumentException("A name must be set if a tracking strategy is specified");
    }
    if (Utils.isSac(this.subscriptionProperties) && this.name == null) {
      throw new IllegalArgumentException("A name must be set if single active consumer is enabled");
    }

    this.environment.maybeInitializeLocator();
    TrackingConfiguration trackingConfiguration;
    if (this.autoTrackingStrategy != null) {
      trackingConfiguration =
          new TrackingConfiguration(
              true,
              true,
              this.autoTrackingStrategy.messageCountBeforeStorage,
              this.autoTrackingStrategy.flushInterval,
              Duration.ZERO);
    } else if (this.manualTrackingStrategy != null) {
      trackingConfiguration =
          new TrackingConfiguration(
              true, false, -1, Duration.ZERO, this.manualTrackingStrategy.checkInterval);
    } else if (this.noTrackingStrategy) {
      trackingConfiguration = DISABLED_TRACKING_CONFIGURATION;
    } else if (this.name != null) {
      // the default tracking strategy
      trackingConfiguration =
          new TrackingConfiguration(true, true, 10_000, Duration.ofSeconds(5), Duration.ZERO);
    } else {
      trackingConfiguration = DISABLED_TRACKING_CONFIGURATION;
    }

    MessageHandler handler;
    if (this.filterConfiguration == null) {
      handler = this.messageHandler;
    } else {
      this.filterConfiguration.validate();
      AtomicInteger i = new AtomicInteger(0);
      this.filterConfiguration.filterValues.forEach(
          v ->
              this.subscriptionProperties.put(
                  SUBSCRIPTION_PROPERTY_FILTER_PREFIX + i.getAndIncrement(), v));
      this.subscriptionProperties.put(
          SUBSCRIPTION_PROPERTY_MATCH_UNFILTERED,
          this.filterConfiguration.matchUnfiltered ? "true" : "false");
      final Predicate filter = this.filterConfiguration.filter;
      final MessageHandler delegate = this.messageHandler;
      handler =
          (context, message) -> {
            if (filter.test(message)) {
              delegate.handle(context, message);
            }
          };
    }

    handler = this.environment.observationCollector().subscribe(handler);

    Consumer consumer;
    if (this.stream != null) {
      consumer =
          new StreamConsumer(
              this.stream,
              this.offsetSpecification,
              handler,
              this.name,
              this.environment,
              trackingConfiguration,
              this.lazyInit,
              this.subscriptionListener,
              this.subscriptionProperties,
              this.consumerUpdateListener,
              this.flowConfiguration.strategy);
      environment.addConsumer((StreamConsumer) consumer);
    } else {
      if (Utils.isSac(this.subscriptionProperties)) {
        this.subscriptionProperties.put(Utils.SUBSCRIPTION_PROPERTY_SUPER_STREAM, this.superStream);
      }
      consumer =
          new SuperStreamConsumer(this, this.superStream, this.environment, trackingConfiguration);
    }
    return consumer;
  }

  StreamConsumerBuilder duplicate() {
    StreamConsumerBuilder duplicate = new StreamConsumerBuilder(this.environment);
    for (Field field : StreamConsumerBuilder.class.getDeclaredFields()) {
      if (Modifier.isStatic(field.getModifiers())) {
        continue;
      }
      field.setAccessible(true);
      try {
        field.set(duplicate, field.get(this));
      } catch (IllegalAccessException e) {
        throw new StreamException("Error while duplicating stream producer builder", e);
      }
    }
    return duplicate;
  }

  static class TrackingConfiguration {

    private final boolean enabled;
    private final boolean auto;

    private final int autoMessageCountBeforeStorage;
    private final Duration autoFlushInterval;
    private final Duration manualCheckInterval;

    TrackingConfiguration(
        boolean enabled,
        boolean auto,
        int autoMessageCountBeforeStorage,
        Duration autoFlushInterval,
        Duration manualCheckInterval) {
      this.enabled = enabled;
      this.auto = auto;
      this.autoMessageCountBeforeStorage = autoMessageCountBeforeStorage;
      this.autoFlushInterval = autoFlushInterval;
      this.manualCheckInterval = manualCheckInterval;
    }

    boolean auto() {
      return this.auto;
    }

    boolean manual() {
      return !auto();
    }

    boolean enabled() {
      return this.enabled;
    }

    public int autoMessageCountBeforeStorage() {
      return autoMessageCountBeforeStorage;
    }

    public Duration autoFlushInterval() {
      return autoFlushInterval;
    }

    public Duration manualCheckInterval() {
      return manualCheckInterval;
    }
  }

  private static final class DefaultAutoTrackingStrategy implements AutoTrackingStrategy {

    private final StreamConsumerBuilder builder;
    private int messageCountBeforeStorage = 10_000;
    private Duration flushInterval = Duration.ofSeconds(5);

    private DefaultAutoTrackingStrategy(StreamConsumerBuilder builder) {
      this.builder = builder;
    }

    @Override
    public AutoTrackingStrategy messageCountBeforeStorage(int messageCountBeforeStorage) {
      if (messageCountBeforeStorage <= 0) {
        throw new IllegalArgumentException(
            "the number of messages before storing must be positive");
      }
      this.messageCountBeforeStorage = messageCountBeforeStorage;
      return this;
    }

    @Override
    public AutoTrackingStrategy flushInterval(Duration flushInterval) {
      if (flushInterval.toMillis() <= 1000) {
        throw new IllegalArgumentException("the flush interval cannot be shorter than 1 second");
      }
      this.flushInterval = flushInterval;
      return this;
    }

    @Override
    public ConsumerBuilder builder() {
      return this.builder;
    }
  }

  private static final class DefaultManualTrackingStrategy implements ManualTrackingStrategy {

    private final StreamConsumerBuilder builder;
    private Duration checkInterval = Duration.ofSeconds(5);

    private DefaultManualTrackingStrategy(StreamConsumerBuilder builder) {
      this.builder = builder;
    }

    @Override
    public ManualTrackingStrategy checkInterval(Duration checkInterval) {
      if (checkInterval.toMillis() <= 1000 && !checkInterval.isZero()) {
        throw new IllegalArgumentException("the check interval cannot be shorter than 1 second");
      }
      this.checkInterval = checkInterval;
      return this;
    }

    @Override
    public ConsumerBuilder builder() {
      return this.builder;
    }
  }

  private static final class DefaultFilterConfiguration implements FilterConfiguration {

    private final StreamConsumerBuilder builder;
    private List filterValues;
    private Predicate filter;
    private boolean matchUnfiltered = false;

    private DefaultFilterConfiguration(StreamConsumerBuilder builder) {
      this.builder = builder;
    }

    @Override
    public FilterConfiguration values(String... filterValues) {
      if (filterValues == null || filterValues.length == 0) {
        throw new IllegalArgumentException("At least one filter value must be specified");
      }
      this.filterValues = Arrays.asList(filterValues);
      return this;
    }

    @Override
    public FilterConfiguration postFilter(Predicate filter) {
      this.filter = filter;
      return this;
    }

    @Override
    public FilterConfiguration matchUnfiltered() {
      this.matchUnfiltered = true;
      return this;
    }

    @Override
    public FilterConfiguration matchUnfiltered(boolean matchUnfiltered) {
      this.matchUnfiltered = matchUnfiltered;
      return this;
    }

    @Override
    public ConsumerBuilder builder() {
      return this.builder;
    }

    private void validate() {
      if (this.filterValues == null || this.filter == null) {
        throw new IllegalArgumentException("Both filter values and the filter logic must be set");
      }
    }
  }

  private static class DefaultFlowConfiguration implements FlowConfiguration {

    private final ConsumerBuilder consumerBuilder;

    private DefaultFlowConfiguration(ConsumerBuilder consumerBuilder) {
      this.consumerBuilder = consumerBuilder;
    }

    private ConsumerFlowStrategy strategy = ConsumerFlowStrategy.creditOnChunkArrival();

    @Override
    public FlowConfiguration initialCredits(int initialCredits) {
      if (initialCredits <= 0) {
        throw new IllegalArgumentException("Credits must be positive");
      }
      this.strategy = ConsumerFlowStrategy.creditOnChunkArrival(initialCredits);
      return this;
    }

    @Override
    public FlowConfiguration strategy(ConsumerFlowStrategy strategy) {
      this.strategy = strategy;
      return this;
    }

    @Override
    public ConsumerBuilder builder() {
      return this.consumerBuilder;
    }
  }

  // to help testing
  public ConsumerUpdateListener consumerUpdateListener() {
    return consumerUpdateListener;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy