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

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

// Copyright (c) 2020-2021 VMware, Inc. or its affiliates.  All rights reserved.
//
// 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 com.rabbitmq.stream.Consumer;
import com.rabbitmq.stream.MessageHandler;
import com.rabbitmq.stream.MessageHandler.Context;
import com.rabbitmq.stream.OffsetSpecification;
import com.rabbitmq.stream.impl.StreamConsumerBuilder.TrackingConfiguration;
import com.rabbitmq.stream.impl.StreamEnvironment.TrackingConsumerRegistration;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.LongConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class StreamConsumer implements Consumer {

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

  private final Runnable closingCallback;

  private final Runnable closingTrackingCallback;

  private final AtomicBoolean closed = new AtomicBoolean(false);

  private final String name;

  private final String stream;

  private final StreamEnvironment environment;

  private volatile Client trackingClient;

  private volatile Status status;

  private final LongConsumer trackingCallback;

  StreamConsumer(
      String stream,
      OffsetSpecification offsetSpecification,
      MessageHandler messageHandler,
      String name,
      StreamEnvironment environment,
      TrackingConfiguration trackingConfiguration) {

    try {
      this.name = name;
      this.stream = stream;
      this.environment = environment;

      MessageHandler messageHandlerWithOrWithoutTracking;
      if (trackingConfiguration.enabled()) {
        TrackingConsumerRegistration trackingConsumerRegistration =
            environment.registerTrackingConsumer(this, trackingConfiguration);

        this.closingTrackingCallback = trackingConsumerRegistration.closingCallback();

        java.util.function.Consumer postMessageProcessingCallback =
            trackingConsumerRegistration.postMessageProcessingCallback();
        if (postMessageProcessingCallback == null) {
          // no callback, no need to decorate
          messageHandlerWithOrWithoutTracking = messageHandler;
        } else {
          messageHandlerWithOrWithoutTracking =
              (context, message) -> {
                messageHandler.handle(context, message);
                postMessageProcessingCallback.accept(context);
              };
        }

        this.trackingCallback = trackingConsumerRegistration.trackingCallback();

      } else {
        this.closingTrackingCallback = () -> {};
        this.trackingCallback = Utils.NO_OP_LONG_CONSUMER;
        messageHandlerWithOrWithoutTracking = messageHandler;
      }

      this.closingCallback =
          environment.registerConsumer(
              this, stream, offsetSpecification, this.name, messageHandlerWithOrWithoutTracking);

      this.status = Status.RUNNING;
    } catch (RuntimeException e) {
      this.closed.set(true);
      throw e;
    }
  }

  @Override
  public void store(long offset) {
    trackingCallback.accept(offset);
    if (canTrack()) {
      try {
        this.trackingClient.storeOffset(this.name, this.stream, offset);
      } catch (Exception e) {
        LOGGER.debug("Error while trying to store offset: {}", e.getMessage());
      }
    }
    // nothing special to do if tracking is not possible or errors, e.g. because of a network
    // failure
    // the tracking strategy will stack the storage request and apply it as soon as it can
  }

  private boolean canTrack() {
    return this.status == Status.RUNNING;
  }

  @Override
  public void close() {
    if (closed.compareAndSet(false, true)) {
      this.environment.removeConsumer(this);
      closeFromEnvironment();
      LOGGER.debug("Closed consumer successfully");
    }
  }

  void closeFromEnvironment() {
    LOGGER.debug("Calling consumer closing callback");
    this.closingCallback.run();
    LOGGER.debug("Calling tracking consumer closing callback (may be no-op)");
    this.closingTrackingCallback.run();
    closed.set(true);
    this.status = Status.CLOSED;
    LOGGER.debug("Closed consumer successfully");
  }

  void closeAfterStreamDeletion() {
    if (closed.compareAndSet(false, true)) {
      this.environment.removeConsumer(this);
      this.status = Status.CLOSED;
    }
  }

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

  synchronized void setClient(Client client) {
    this.trackingClient = client;
  }

  synchronized void unavailable() {
    this.status = Status.NOT_AVAILABLE;
    this.trackingClient = null;
  }

  void running() {
    this.status = Status.RUNNING;
  }

  long lastStoredOffset() {
    if (canTrack()) {
      try {
        // the client can be null by now, but we catch the exception and return 0
        // callers should know how to deal with a stored offset of 0
        return this.trackingClient.queryOffset(this.name, this.stream);
      } catch (Exception e) {
        return 0;
      }
    } else {
      return 0;
    }
  }

  String stream() {
    return this.stream;
  }

  enum Status {
    RUNNING,
    NOT_AVAILABLE,
    CLOSED
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy