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

io.vertx.amqpbridge.impl.AmqpConsumerImpl Maven / Gradle / Ivy

There is a newer version: 3.9.16
Show newest version
/*
* Copyright 2016 the original author or authors.
*
* 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 io.vertx.amqpbridge.impl;

import java.util.ArrayDeque;
import java.util.Queue;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.VertxException;
import io.vertx.core.eventbus.Message;
import io.vertx.core.eventbus.MessageConsumer;
import io.vertx.core.eventbus.impl.BodyReadStream;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.streams.ReadStream;
import io.vertx.proton.ProtonConnection;
import io.vertx.proton.ProtonReceiver;

public class AmqpConsumerImpl implements MessageConsumer {

  private static final Logger LOG = LoggerFactory.getLogger(AmqpConsumerImpl.class);

  private final AmqpBridgeImpl bridge;
  private final ProtonReceiver receiver;
  private final String amqpAddress;
  private final MessageTranslatorImpl translator = new MessageTranslatorImpl();
  private final Queue buffered = new ArrayDeque<>();
  private Handler> handler;
  private boolean paused;
  private boolean closed;
  private Handler exceptionHandler;
  private Handler endHandler;
  private boolean initialCreditGiven;
  private int initialCredit = 1000;

  public AmqpConsumerImpl(AmqpBridgeImpl bridge, ProtonConnection connection, String amqpAddress) {
    if(!bridge.onContextEventLoop()) {
      throw new IllegalStateException("Consumer creation was not executed on the bridge context thread");
    }

    this.bridge = bridge;
    this.amqpAddress = amqpAddress;
    receiver = connection.createReceiver(amqpAddress);
    receiver.closeHandler(res -> {
      Handler endh = null;
      Handler exh = null;
      boolean closeReceiver = false;

      synchronized (AmqpConsumerImpl.this) {
        if (!closed && endHandler != null) {
          endh = endHandler;
        } else if (!closed && exceptionHandler != null) {
          exh = exceptionHandler;
        }

        if(!closed) {
          closed = true;
          closeReceiver = true;
        }
      }

      if (endh != null) {
        endh.handle(null);
      } else if (exh != null) {
        if (res.succeeded()) {
          exh.handle(new VertxException("Consumer closed remotely"));
        } else {
          exh.handle(new VertxException("Consumer closed remotely with error", res.cause()));
        }
      } else {
        if (res.succeeded()) {
          LOG.warn("Consumer for address " + amqpAddress + " unexpectedly closed remotely");
        } else {
          LOG.warn("Consumer for address " + amqpAddress + " unexpectedly closed remotely with error", res.cause());
        }
      }

      if(closeReceiver) {
        receiver.close();
      }
    });
    receiver.handler((delivery, protonMessage) -> {
      JsonObject body = translator.convertToJsonObject(protonMessage);
      AmqpMessageImpl vertxMessage = new AmqpMessageImpl(body, this.bridge, protonMessage, delivery, amqpAddress,
          protonMessage.getReplyTo());

      handleMessage(vertxMessage);
    });
    // Disable auto-accept and automated prefetch, we will manage disposition and credit
    // manually to allow for delayed handler registration and pause/resume functionality.
    receiver.setAutoAccept(false);
    receiver.setPrefetch(0);

    receiver.open();
  }

  private void handleMessage(AmqpMessageImpl vertxMessage) {
    Handler> h = null;
    boolean schedule = false;

    synchronized (AmqpConsumerImpl.this) {
      if (handler != null && !paused && buffered.isEmpty()) {
        h = handler;
      } else if (handler != null && !paused) {
        // Buffered messages present, deliver the oldest of those instead
        buffered.add(vertxMessage);
        vertxMessage = buffered.poll();
        h = handler;

        // Schedule a delivery for the next buffered message
        schedule = true;
      } else {
        // Buffer message until we have a handler or aren't paused
        buffered.add(vertxMessage);
      }
    }

    // Delivering outside the synchronized block
    if (h != null) {
      deliverMessageToHandler(vertxMessage, h);
    }

    // schedule next delivery if appropriate, after earlier delivery to allow chance to pause etc.
    if(schedule) {
      scheduleBufferedMessageDelivery();
    }
  }

  private void deliverMessageToHandler(AmqpMessageImpl vertxMessage, Handler> h) {
    h.handle(vertxMessage);
    vertxMessage.accept();
    receiver.flow(1);
  }

  private void scheduleBufferedMessageDelivery() {
    boolean schedule = false;

    synchronized (AmqpConsumerImpl.this) {
      schedule = !buffered.isEmpty() && !paused;
    }

    if (schedule) {
      bridge.runOnContext(false, v -> {
        Handler> h = null;
        AmqpMessageImpl message = null;

        synchronized (AmqpConsumerImpl.this) {
          h = handler;
          if (h != null && !paused) {
            message = buffered.poll();
          }
        }

        if (message != null) {
          // Delivering outside the synchronized block
          deliverMessageToHandler(message, h);

          // Schedule a delivery for a further buffered message if any
          scheduleBufferedMessageDelivery();
        }
      });
    }
  }

  @Override
  public synchronized MessageConsumer exceptionHandler(Handler handler) {
    exceptionHandler = handler;
    return this;
  }

  @Override
  public MessageConsumer handler(final Handler> handler) {
    int creditToFlow = 0;
    boolean schedule = false;

    synchronized (AmqpConsumerImpl.this) {
      this.handler = handler;
      if (handler != null) {
        schedule = true;

        // Flow initial credit if needed
        if (!initialCreditGiven) {
          initialCreditGiven = true;
          creditToFlow = initialCredit;
        }
      }
    }

    if(creditToFlow > 0) {
      final int c = creditToFlow;
      bridge.runOnContext(true, v -> {
        receiver.flow(c);
      });
    }

    if(schedule) {
      scheduleBufferedMessageDelivery();
    }

    return this;
  }

  @Override
  public synchronized MessageConsumer pause() {
    paused = true;
    return this;
  }

  @Override
  public synchronized MessageConsumer resume() {
    paused = false;
    scheduleBufferedMessageDelivery();
    return this;
  }

  @Override
  public synchronized MessageConsumer endHandler(Handler endHandler) {
    this.endHandler = endHandler;
    return this;
  }

  @Override
  public ReadStream bodyStream() {
    return new BodyReadStream<>(this);
  }

  @Override
  public synchronized boolean isRegistered() {
    return handler != null;
  }

  @Override
  public String address() {
    return amqpAddress;
  }

  @Override
  public synchronized MessageConsumer setMaxBufferedMessages(int maxBufferedMessages) {
    if(!initialCreditGiven) {
      initialCredit = maxBufferedMessages;
    }

    return this;
  }

  @Override
  public synchronized int getMaxBufferedMessages() {
    return initialCredit;
  }

  @Override
  public void completionHandler(Handler> completionHandler) {
    throw new UnsupportedOperationException("Registration completion handler is not supported by this consumer");
  }

  @Override
  public synchronized void unregister() {
    unregister(null);
  }

  @Override
  public synchronized void unregister(Handler> completionHandler) {
    handler = null;
    closed = true;

    bridge.runOnContext(true, v -> {
      if (completionHandler != null) {
        receiver.closeHandler((result) -> {
          if (result.succeeded()) {
            completionHandler.handle(Future.succeededFuture());
          } else {
            completionHandler.handle(Future.failedFuture(result.cause()));
          }
        });
      } else {
        receiver.closeHandler(null);
      }
      receiver.close();
    });
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy