com.espertech.esperio.amqp.QueueingConsumer Maven / Gradle / Ivy
/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package com.espertech.esperio.amqp;
import com.rabbitmq.client.*;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.utility.Utility;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Convenience class: an implementation of {@link Consumer} with
* straightforward blocking semantics.
*
* The general pattern for using QueueingConsumer is as follows:
*
*
* // Create connection and channel.
* {@link ConnectionFactory} factory = new ConnectionFactory();
* Connection conn = factory.newConnection();
* {@link Channel} ch1 = conn.createChannel();
*
* // Declare a queue and bind it to an exchange.
* String queueName = ch1.queueDeclare().{@link AMQP.Queue.DeclareOk#getQueue getQueue}();
* ch1.{@link Channel#queueBind queueBind}(queueName, exchangeName, queueName);
*
* // Create the QueueingConsumer and have it consume from the queue
* QueueingConsumer consumer = new {@link QueueingConsumer#QueueingConsumer(Channel) QueueingConsumer}(ch1);
* ch1.{@link Channel#basicConsume basicConsume}(queueName, false, consumer);
*
* // Process deliveries
* while (/* some condition * /) {
* {@link QueueingConsumer.Delivery} delivery = consumer.{@link QueueingConsumer#nextDelivery nextDelivery}();
* // process delivery
* ch1.{@link Channel#basicAck basicAck}(delivery.{@link QueueingConsumer.Delivery#getEnvelope getEnvelope}().{@link Envelope#getDeliveryTag getDeliveryTag}(), false);
* }
*
*
*
* For a more complete example, see LogTail in the test/src/com/rabbitmq/examples
* directory of the source distribution.
* deprecated QueueingConsumer
was introduced to allow
* applications to overcome a limitation in the way Connection
* managed threads and consumer dispatching. When QueueingConsumer
* was introduced, callbacks to Consumers
were made on the
* Connection's
thread. This had two main drawbacks. Firstly, the
* Consumer
could stall the processing of all
* Channels
on the Connection
. Secondly, if a
* Consumer
made a recursive synchronous call into its
* Channel
the client would deadlock.
* QueueingConsumer
provided client code with an easy way to
* obviate this problem by queueing incoming messages and processing them on
* a separate, application-managed thread.
* The threading behaviour of Connection
and Channel
* has been changed so that each Channel
uses a distinct thread
* for dispatching to Consumers
. This prevents
* Consumers
on one Channel
holding up
* Consumers
on another and it also prevents recursive calls from
* deadlocking the client.
* As such, it is now safe to implement Consumer
directly or
* to extend DefaultConsumer
.
*/
public class QueueingConsumer extends DefaultConsumer {
private final BlockingQueue queue;
// When this is non-null the queue is in shutdown mode and nextDelivery should
// throw a shutdown signal exception.
private volatile ShutdownSignalException shutdown;
private volatile ConsumerCancelledException cancelled;
// Marker object used to signal the queue is in shutdown mode.
// It is only there to wake up consumers. The canonical representation
// of shutting down is the presence of _shutdown.
// Invariant: This is never on _queue unless _shutdown != null.
private static final Delivery POISON = new Delivery(null, null, null);
public QueueingConsumer(Channel ch) {
this(ch, new LinkedBlockingQueue());
}
public QueueingConsumer(Channel ch, BlockingQueue q) {
super(ch);
this.queue = q;
}
@Override
public void handleShutdownSignal(String consumerTag,
ShutdownSignalException sig) {
shutdown = sig;
queue.add(POISON);
}
@Override
public void handleCancel(String consumerTag) throws IOException {
cancelled = new ConsumerCancelledException();
queue.add(POISON);
}
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
checkShutdown();
this.queue.add(new Delivery(envelope, properties, body));
}
/**
* Encapsulates an arbitrary message - simple "bean" holder structure.
*/
public static class Delivery {
private final Envelope envelope;
private final AMQP.BasicProperties properties;
private final byte[] body;
public Delivery(Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
this.envelope = envelope;
this.properties = properties;
this.body = body;
}
/**
* Retrieve the message envelope.
*
* @return the message envelope
*/
public Envelope getEnvelope() {
return envelope;
}
/**
* Retrieve the message properties.
*
* @return the message properties
*/
public BasicProperties getProperties() {
return properties;
}
/**
* Retrieve the message body.
*
* @return the message body
*/
public byte[] getBody() {
return body;
}
}
/**
* Check if we are in shutdown mode and if so throw an exception.
*/
private void checkShutdown() {
if (shutdown != null)
throw Utility.fixStackTrace(shutdown);
}
/**
* If delivery is not POISON nor null, return it.
*
* If delivery, _shutdown and _cancelled are all null, return null.
*
* If delivery is POISON re-insert POISON into the queue and
* throw an exception if POISONed for no reason.
*
* Otherwise, if we are in shutdown mode or cancelled,
* throw a corresponding exception.
*/
private Delivery handle(Delivery delivery) {
if (delivery == POISON ||
delivery == null && (shutdown != null || cancelled != null)) {
if (delivery == POISON) {
queue.add(POISON);
if (shutdown == null && cancelled == null) {
throw new IllegalStateException(
"POISON in queue, but null _shutdown and null _cancelled. " +
"This should never happen, please report as a BUG");
}
}
if (null != shutdown)
throw Utility.fixStackTrace(shutdown);
if (null != cancelled)
throw Utility.fixStackTrace(cancelled);
}
return delivery;
}
/**
* Main application-side API: wait for the next message delivery and return it.
*
* @return the next message
* @throws InterruptedException if an interrupt is received while waiting
* @throws ShutdownSignalException if the connection is shut down while waiting
* @throws ConsumerCancelledException if this consumer is cancelled while waiting
*/
public Delivery nextDelivery()
throws InterruptedException, ShutdownSignalException, ConsumerCancelledException {
return handle(queue.take());
}
/**
* Main application-side API: wait for the next message delivery and return it.
*
* @param timeout timeout in millisecond
* @return the next message or null if timed out
* @throws InterruptedException if an interrupt is received while waiting
* @throws ShutdownSignalException if the connection is shut down while waiting
* @throws ConsumerCancelledException if this consumer is cancelled while waiting
*/
public Delivery nextDelivery(long timeout)
throws InterruptedException, ShutdownSignalException, ConsumerCancelledException {
return handle(queue.poll(timeout, TimeUnit.MILLISECONDS));
}
}