/*
* Copyright 2002-2017 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 org.springframework.amqp.rabbit.listener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.OptionalLong;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.amqp.AmqpAuthenticationException;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.AmqpIOException;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.ChannelProxy;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
import org.springframework.amqp.rabbit.connection.RabbitResourceHolder;
import org.springframework.amqp.rabbit.connection.RabbitUtils;
import org.springframework.amqp.rabbit.listener.exception.FatalListenerStartupException;
import org.springframework.amqp.rabbit.support.ConsumerCancelledException;
import org.springframework.amqp.rabbit.support.Delivery;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.RabbitExceptionTranslator;
import org.springframework.amqp.support.ConsumerTagStrategy;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.ObjectUtils;
import org.springframework.util.backoff.BackOffExecution;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AlreadyClosedException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.ShutdownSignalException;
import com.rabbitmq.utility.Utility;
/**
* Specialized consumer encapsulating knowledge of the broker
* connections and having its own lifecycle (start and stop).
*
* @author Mark Pollack
* @author Dave Syer
* @author Gary Russell
* @author Casper Mout
* @author Artem Bilan
* @author Alex Panchenko
*/
public class BlockingQueueConsumer {
private static Log logger = LogFactory.getLog(BlockingQueueConsumer.class);
private final BlockingQueue<Delivery> queue;
// When this is non-null the connection has been closed (should never happen in normal operation).
private volatile ShutdownSignalException shutdown;
private final String[] queues;
private final int prefetchCount;
private final boolean transactional;
private Channel channel;
private RabbitResourceHolder resourceHolder;
private InternalConsumer consumer;
/**
* The flag indicating that consumer has been cancelled from all queues
* via {@code handleCancelOk} callback replies.
*/
private final AtomicBoolean cancelled = new AtomicBoolean(false);
private final AcknowledgeMode acknowledgeMode;
private final ConnectionFactory connectionFactory;
private final MessagePropertiesConverter messagePropertiesConverter;
private final ActiveObjectCounter<BlockingQueueConsumer> activeObjectCounter;
private final Map<String, Object> consumerArgs = new HashMap<String, Object>();
private final boolean exclusive;
private final Set<Long> deliveryTags = new LinkedHashSet<Long>();
private final boolean defaultRequeueRejected;
private final Map<String, String> consumerTags = new ConcurrentHashMap<String, String>();
private final Set<String> missingQueues = Collections.synchronizedSet(new HashSet<String>());
private long retryDeclarationInterval = 60000;
private long failedDeclarationRetryInterval =
AbstractMessageListenerContainer.DEFAULT_FAILED_DECLARATION_RETRY_INTERVAL;
private int declarationRetries = 3;
private long lastRetryDeclaration;
private ConsumerTagStrategy tagStrategy;
private BackOffExecution backOffExecution;
private long shutdownTimeout;
private boolean locallyTransacted;
private volatile long abortStarted;
private volatile boolean normalCancel;
/**
* Create a consumer. The consumer must not attempt to use
* the connection factory or communicate with the broker
* until it is started. RequeueRejected defaults to true.
* @param connectionFactory The connection factory.
* @param messagePropertiesConverter The properties converter.
* @param activeObjectCounter The active object counter; used during shutdown.
* @param acknowledgeMode The acknowledgemode.
* @param transactional Whether the channel is transactional.
* @param prefetchCount The prefetch count.
* @param queues The queues.
*/
public BlockingQueueConsumer(ConnectionFactory connectionFactory,
MessagePropertiesConverter messagePropertiesConverter,
ActiveObjectCounter<BlockingQueueConsumer> activeObjectCounter, AcknowledgeMode acknowledgeMode,
boolean transactional, int prefetchCount, String... queues) {
this(connectionFactory, messagePropertiesConverter, activeObjectCounter,
acknowledgeMode, transactional, prefetchCount, true, queues);
}
/**
* Create a consumer. The consumer must not attempt to use
* the connection factory or communicate with the broker
* until it is started.
* @param connectionFactory The connection factory.
* @param messagePropertiesConverter The properties converter.
* @param activeObjectCounter The active object counter; used during shutdown.
* @param acknowledgeMode The acknowledge mode.
* @param transactional Whether the channel is transactional.
* @param prefetchCount The prefetch count.
* @param defaultRequeueRejected true to reject requeued messages.
* @param queues The queues.
*/
public BlockingQueueConsumer(ConnectionFactory connectionFactory,
MessagePropertiesConverter messagePropertiesConverter,
ActiveObjectCounter<BlockingQueueConsumer> activeObjectCounter, AcknowledgeMode acknowledgeMode,
boolean transactional, int prefetchCount, boolean defaultRequeueRejected, String... queues) {
this(connectionFactory, messagePropertiesConverter, activeObjectCounter, acknowledgeMode, transactional,
prefetchCount, defaultRequeueRejected, null, queues);
}
/**
* Create a consumer. The consumer must not attempt to use the
* connection factory or communicate with the broker
* until it is started.
* @param connectionFactory The connection factory.
* @param messagePropertiesConverter The properties converter.
* @param activeObjectCounter The active object counter; used during shutdown.
* @param acknowledgeMode The acknowledge mode.
* @param transactional Whether the channel is transactional.
* @param prefetchCount The prefetch count.
* @param defaultRequeueRejected true to reject requeued messages.
* @param consumerArgs The consumer arguments (e.g. x-priority).
* @param queues The queues.
*/
public BlockingQueueConsumer(ConnectionFactory connectionFactory,
MessagePropertiesConverter messagePropertiesConverter,
ActiveObjectCounter<BlockingQueueConsumer> activeObjectCounter, AcknowledgeMode acknowledgeMode,
boolean transactional, int prefetchCount, boolean defaultRequeueRejected,
Map<String, Object> consumerArgs, String... queues) {
this(connectionFactory, messagePropertiesConverter, activeObjectCounter, acknowledgeMode, transactional,
prefetchCount, defaultRequeueRejected, consumerArgs, false, queues);
}
/**
* Create a consumer. The consumer must not attempt to use
* the connection factory or communicate with the broker
* until it is started.
* @param connectionFactory The connection factory.
* @param messagePropertiesConverter The properties converter.
* @param activeObjectCounter The active object counter; used during shutdown.
* @param acknowledgeMode The acknowledge mode.
* @param transactional Whether the channel is transactional.
* @param prefetchCount The prefetch count.
* @param defaultRequeueRejected true to reject requeued messages.
* @param consumerArgs The consumer arguments (e.g. x-priority).
* @param exclusive true if the consumer is to be exclusive.
* @param queues The queues.
*/
public BlockingQueueConsumer(ConnectionFactory connectionFactory,
MessagePropertiesConverter messagePropertiesConverter,
ActiveObjectCounter<BlockingQueueConsumer> activeObjectCounter, AcknowledgeMode acknowledgeMode,
boolean transactional, int prefetchCount, boolean defaultRequeueRejected,
Map<String, Object> consumerArgs, boolean exclusive, String... queues) {
this.connectionFactory = connectionFactory;
this.messagePropertiesConverter = messagePropertiesConverter;
this.activeObjectCounter = activeObjectCounter;
this.acknowledgeMode = acknowledgeMode;
this.transactional = transactional;
this.prefetchCount = prefetchCount;
this.defaultRequeueRejected = defaultRequeueRejected;
if (consumerArgs != null && consumerArgs.size() > 0) {
this.consumerArgs.putAll(consumerArgs);
}
this.exclusive = exclusive;
this.queues = Arrays.copyOf(queues, queues.length);
this.queue = new LinkedBlockingQueue<Delivery>(prefetchCount);
}
public Channel getChannel() {
return this.channel;
}
public String getConsumerTag() {
return this.consumer.getConsumerTag();
}
public void setShutdownTimeout(long shutdownTimeout) {
this.shutdownTimeout = shutdownTimeout;
}
/**
* Set the number of retries after passive queue declaration fails.
* @param declarationRetries The number of retries, default 3.
* @since 1.3.9
* @see #setFailedDeclarationRetryInterval(long)
*/
public void setDeclarationRetries(int declarationRetries) {
this.declarationRetries = declarationRetries;
}
/**
* Set the interval between passive queue declaration attempts in milliseconds.
* @param failedDeclarationRetryInterval the interval, default 5000.
* @since 1.3.9
* @see #setDeclarationRetries(int)
*/
public void setFailedDeclarationRetryInterval(long failedDeclarationRetryInterval) {
this.failedDeclarationRetryInterval = failedDeclarationRetryInterval;
}
/**
* When consuming multiple queues, set the interval between declaration attempts when only
* a subset of the queues were available (milliseconds).
* @param retryDeclarationInterval the interval, default 60000.
* @since 1.3.9
*/
public void setRetryDeclarationInterval(long retryDeclarationInterval) {
this.retryDeclarationInterval = retryDeclarationInterval;
}
/**
* Set the {@link ConsumerTagStrategy} to use when generating consumer tags.
* @param tagStrategy the tagStrategy to set
* @since 1.4.5
*/
public void setTagStrategy(ConsumerTagStrategy tagStrategy) {
this.tagStrategy = tagStrategy;
}
/**
* Set the {@link BackOffExecution} to use for the recovery in the {@code SimpleMessageListenerContainer}.
* @param backOffExecution the backOffExecution.
* @since 1.5
*/
public void setBackOffExecution(BackOffExecution backOffExecution) {
this.backOffExecution = backOffExecution;
}
public BackOffExecution getBackOffExecution() {
return this.backOffExecution;
}
/**
* True if the channel is locally transacted.
* @param locallyTransacted the locally transacted to set.
* @since 1.6.6
*/
public void setLocallyTransacted(boolean locallyTransacted) {
this.locallyTransacted = locallyTransacted;
}
/**
* Clear the delivery tags when rolling back with an external transaction
* manager.
* @since 1.6.6
*/
public void clearDeliveryTags() {
this.deliveryTags.clear();
}
/**
* Return true if cancellation is expected.
* @return true if expected.
*/
public boolean isNormalCancel() {
return this.normalCancel;
}
/**
* Return the size the queues array.
* @return the count.
*/
int getQueueCount() {
return this.queues.length;
}
protected void basicCancel() {
basicCancel(false);
}
protected void basicCancel(boolean expected) {
this.normalCancel = expected;
for (String consumerTag : this.consumerTags.keySet()) {
removeConsumer(consumerTag);
try {
if (this.channel.isOpen()) {
this.channel.basicCancel(consumerTag);
}
}
catch (IOException | IllegalStateException e) {
if (logger.isDebugEnabled()) {
logger.debug("Error performing 'basicCancel'", e);
}
}
catch (AlreadyClosedException e) {
if (logger.isTraceEnabled()) {
logger.trace(this.channel + " is already closed");
}
}
}
this.abortStarted = System.currentTimeMillis();
}
protected boolean hasDelivery() {
return !this.queue.isEmpty();
}
protected boolean cancelled() {
return this.cancelled.get() || (this.abortStarted > 0 &&
this.abortStarted + this.shutdownTimeout > System.currentTimeMillis());
}
/**
* Check if we are in shutdown mode and if so throw an exception.
*/
private void checkShutdown() {
if (this.shutdown != null) {
throw Utility.fixStackTrace(this.shutdown);
}
}
/**
* If this is a non-POISON non-null delivery simply return it.
* If this is POISON we are in shutdown mode, throw
* shutdown. If delivery is null, we may be in shutdown mode. Check and see.
* @param delivery the delivered message contents.
* @return A message built from the contents.
* @throws InterruptedException if the thread is interrupted.
*/
private Message handle(Delivery delivery) throws InterruptedException {
if ((delivery == null && this.shutdown != null)) {
throw this.shutdown;
}
if (delivery == null) {
return null;
}
byte[] body = delivery.getBody();
Envelope envelope = delivery.getEnvelope();
MessageProperties messageProperties = this.messagePropertiesConverter.toMessageProperties(
delivery.getProperties(), envelope, "UTF-8");
messageProperties.setConsumerTag(delivery.getConsumerTag());
messageProperties.setConsumerQueue(this.consumerTags.get(delivery.getConsumerTag()));
Message message = new Message(body, messageProperties);
if (logger.isDebugEnabled()) {
logger.debug("Received message: " + message);
}
this.deliveryTags.add(messageProperties.getDeliveryTag());
if (this.transactional && !this.locallyTransacted) {
ConnectionFactoryUtils.registerDeliveryTag(this.connectionFactory, this.channel,
delivery.getEnvelope().getDeliveryTag());
}
return message;
}
/**
* 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
*/
public Message nextMessage() throws InterruptedException, ShutdownSignalException {
logger.trace("Retrieving delivery for " + this);
return handle(this.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
*/
public Message nextMessage(long timeout) throws InterruptedException, ShutdownSignalException {
if (logger.isDebugEnabled()) {
logger.debug("Retrieving delivery for " + this);
}
checkShutdown();
if (this.missingQueues.size() > 0) {
checkMissingQueues();
}
Message message = handle(this.queue.poll(timeout, TimeUnit.MILLISECONDS));
if (message == null && this.cancelled.get()) {
throw new ConsumerCancelledException();
}
return message;
}
/*
* Check to see if missing queues are now available; use a separate channel so the main
* channel is not closed by the broker if the declaration fails.
*/
private void checkMissingQueues() {
long now = System.currentTimeMillis();
if (now - this.retryDeclarationInterval > this.lastRetryDeclaration) {
synchronized (this.missingQueues) {
Iterator<String> iterator = this.missingQueues.iterator();
while (iterator.hasNext()) {
boolean available = true;
String queue = iterator.next();
Channel channel = null;
try {
channel = this.connectionFactory.createConnection().createChannel(false);
channel.queueDeclarePassive(queue);
if (logger.isInfoEnabled()) {
logger.info("Queue '" + queue + "' is now available");
}
}
catch (IOException e) {
available = false;
if (logger.isWarnEnabled()) {
logger.warn("Queue '" + queue + "' is still not available");
}
}
finally {
if (channel != null) {
try {
channel.close();
}
catch (IOException e) {
//Ignore it
}
catch (TimeoutException e) {
//Ignore it
}
}
}
if (available) {
try {
this.consumeFromQueue(queue);
iterator.remove();
}
catch (IOException e) {
throw RabbitExceptionTranslator.convertRabbitAccessException(e);
}
}
}
}
this.lastRetryDeclaration = now;
}
}
public void start() throws AmqpException {
if (logger.isDebugEnabled()) {
logger.debug("Starting consumer " + this);
}
try {
this.resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(this.connectionFactory,
this.transactional);
this.channel = this.resourceHolder.getChannel();
}
catch (AmqpAuthenticationException e) {
throw new FatalListenerStartupException("Authentication failure", e);
}
this.consumer = new InternalConsumer(this.channel);
this.deliveryTags.clear();
this.activeObjectCounter.add(this);
// mirrored queue might be being moved
int passiveDeclareRetries = this.declarationRetries;
do {
try {
attemptPassiveDeclarations();
if (passiveDeclareRetries < this.declarationRetries && logger.isInfoEnabled()) {
logger.info("Queue declaration succeeded after retrying");
}
passiveDeclareRetries = 0;
}
catch (DeclarationException e) {
if (passiveDeclareRetries > 0 && this.channel.isOpen()) {
if (logger.isWarnEnabled()) {
logger.warn("Queue declaration failed; retries left=" + (passiveDeclareRetries), e);
try {
Thread.sleep(this.failedDeclarationRetryInterval);
}
catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
}
}
else if (e.getFailedQueues().size() < this.queues.length) {
if (logger.isWarnEnabled()) {
logger.warn("Not all queues are available; only listening on those that are - configured: "
+ Arrays.asList(this.queues) + "; not available: " + e.getFailedQueues());
}
this.missingQueues.addAll(e.getFailedQueues());
this.lastRetryDeclaration = System.currentTimeMillis();
}
else {
this.activeObjectCounter.release(this);
throw new QueuesNotAvailableException("Cannot prepare queue for listener. "
+ "Either the queue doesn't exist or the broker will not allow us to use it.", e);
}
}
}
while (passiveDeclareRetries-- > 0);
if (!this.acknowledgeMode.isAutoAck()) {
// Set basicQos before calling basicConsume (otherwise if we are not acking the broker
// will send blocks of 100 messages)
try {
this.channel.basicQos(this.prefetchCount);
}
catch (IOException e) {
this.activeObjectCounter.release(this);
throw new AmqpIOException(e);
}
}
try {
for (String queueName : this.queues) {
if (!this.missingQueues.contains(queueName)) {
consumeFromQueue(queueName);
}
}
}
catch (IOException e) {
throw RabbitExceptionTranslator.convertRabbitAccessException(e);
}
}
private void consumeFromQueue(String queue) throws IOException {
String consumerTag = this.channel.basicConsume(queue, this.acknowledgeMode.isAutoAck(),
(this.tagStrategy != null ? this.tagStrategy.createConsumerTag(queue) : ""), false, this.exclusive,
this.consumerArgs, this.consumer);
if (consumerTag != null) {
this.consumerTags.put(consumerTag, queue);
if (logger.isDebugEnabled()) {
logger.debug("Started on queue '" + queue + "' with tag " + consumerTag + ": " + this);
}
}
else {
logger.error("Null consumer tag received for queue " + queue);
}
}
private void attemptPassiveDeclarations() {
DeclarationException failures = null;
for (String queueName : this.queues) {
try {
try {
this.channel.queueDeclarePassive(queueName);
}
catch (IllegalArgumentException e) {
try {
if (this.channel instanceof ChannelProxy) {
((ChannelProxy) this.channel).getTargetChannel().close();
}
}
catch (TimeoutException e1) {
}
throw new FatalListenerStartupException("Illegal Argument on Queue Declaration", e);
}
}
catch (IOException e) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to declare queue:" + queueName);
}
if (!this.channel.isOpen()) {
throw new AmqpIOException(e);
}
if (failures == null) {
failures = new DeclarationException(e);
}
failures.addFailedQueue(queueName);
}
}
if (failures != null) {
throw failures;
}
}
public void stop() {
if (this.abortStarted == 0) { // signal handle delivery to use offer
this.abortStarted = System.currentTimeMillis();
}
if (this.consumer != null && this.consumer.getChannel() != null && this.consumerTags.size() > 0
&& !this.cancelled.get()) {
try {
RabbitUtils.closeMessageConsumer(this.consumer.getChannel(), this.consumerTags.keySet(),
this.transactional);
}
catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Error closing consumer " + this, e);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Closing Rabbit Channel: " + this.channel);
}
RabbitUtils.setPhysicalCloseRequired(true);
ConnectionFactoryUtils.releaseResources(this.resourceHolder);
this.deliveryTags.clear();
this.consumer = null;
this.queue.clear(); // in case we still have a client thread blocked
}
/**
* Perform a rollback, handling rollback exceptions properly.
* @param ex the thrown application exception or error
* @throws Exception in case of a rollback error
*/
public void rollbackOnExceptionIfNecessary(Throwable ex) throws Exception {
boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();
try {
if (this.transactional) {
if (logger.isDebugEnabled()) {
logger.debug("Initiating transaction rollback on application exception: " + ex);
}
RabbitUtils.rollbackIfNecessary(this.channel);
}
if (ackRequired) {
OptionalLong deliveryTag = this.deliveryTags.stream().mapToLong(l -> l).max();
if (deliveryTag.isPresent()) {
this.channel.basicNack(deliveryTag.getAsLong(), true,
RabbitUtils.shouldRequeue(this.defaultRequeueRejected, ex, logger));
}
if (this.transactional) {
// Need to commit the reject (=nack)
RabbitUtils.commitIfNecessary(this.channel);
}
}
}
catch (Exception e) {
logger.error("Application exception overridden by rollback exception", ex);
throw e;
}
finally {
this.deliveryTags.clear();
}
}
/**
* Remove the consumer and set cancelled if all are gone.
* @param consumerTag the tag to remove.
* @return true if consumers remain.
*/
private boolean removeConsumer(String consumerTag) {
this.consumerTags.remove(consumerTag);
if (this.consumerTags.isEmpty()) {
this.cancelled.set(true);
return false;
}
else {
return true;
}
}
/**
* Perform a commit or message acknowledgement, as appropriate.
* @param locallyTransacted Whether the channel is locally transacted.
* @return true if at least one delivery tag exists.
* @throws IOException Any IOException.
*/
public boolean commitIfNecessary(boolean locallyTransacted) throws IOException {
if (this.deliveryTags.isEmpty()) {
return false;
}
/*
* If we have a TX Manager, but no TX, act like we are locally transacted.
*/
boolean isLocallyTransacted = locallyTransacted
|| (this.transactional
&& TransactionSynchronizationManager.getResource(this.connectionFactory) == null);
try {
boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();
if (ackRequired) {
if (!this.transactional || isLocallyTransacted) {
long deliveryTag = new ArrayList<Long>(this.deliveryTags).get(this.deliveryTags.size() - 1);
this.channel.basicAck(deliveryTag, true);
}
}
if (isLocallyTransacted) {
// For manual acks we still need to commit
RabbitUtils.commitIfNecessary(this.channel);
}
}
finally {
this.deliveryTags.clear();
}
return true;
}
@Override
public String toString() {
return "Consumer@" + ObjectUtils.getIdentityHexString(this) + ": "
+ "tags=[" + (this.consumerTags.toString()) + "], channel=" + this.channel
+ ", acknowledgeMode=" + this.acknowledgeMode + " local queue size=" + this.queue.size();
}
private final class InternalConsumer extends DefaultConsumer {
InternalConsumer(Channel channel) {
super(channel);
}
@Override
public void handleConsumeOk(String consumerTag) {
super.handleConsumeOk(consumerTag);
if (logger.isDebugEnabled()) {
logger.debug("ConsumeOK : " + BlockingQueueConsumer.this);
}
}
@Override
public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
if (logger.isDebugEnabled()) {
if (RabbitUtils.isNormalShutdown(sig)) {
logger.debug("Received shutdown signal for consumer tag=" + consumerTag + ": " + sig.getMessage());
}
else {
logger.debug("Received shutdown signal for consumer tag=" + consumerTag, sig);
}
}
BlockingQueueConsumer.this.shutdown = sig;
// The delivery tags will be invalid if the channel shuts down
BlockingQueueConsumer.this.deliveryTags.clear();
BlockingQueueConsumer.this.activeObjectCounter.release(BlockingQueueConsumer.this);
}
@Override
public void handleCancel(String consumerTag) throws IOException {
if (logger.isWarnEnabled()) {
logger.warn("Cancel received for " + consumerTag + " ("
+ BlockingQueueConsumer.this.consumerTags.get(consumerTag)
+ "); " + BlockingQueueConsumer.this);
}
if (removeConsumer(consumerTag)) {
basicCancel(false);
}
}
@Override
public void handleCancelOk(String consumerTag) {
if (logger.isDebugEnabled()) {
logger.debug("Received cancelOk for tag " + consumerTag + " ("
+ BlockingQueueConsumer.this.consumerTags.get(consumerTag)
+ "); " + BlockingQueueConsumer.this);
}
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Storing delivery for " + BlockingQueueConsumer.this);
}
try {
if (BlockingQueueConsumer.this.abortStarted > 0) {
if (!BlockingQueueConsumer.this.queue.offer(new Delivery(consumerTag, envelope, properties, body),
BlockingQueueConsumer.this.shutdownTimeout, TimeUnit.MILLISECONDS)) {
RabbitUtils.setPhysicalCloseRequired(true);
// Defensive - should never happen
BlockingQueueConsumer.this.queue.clear();
getChannel().basicNack(envelope.getDeliveryTag(), true, true);
getChannel().basicCancel(consumerTag);
try {
getChannel().close();
}
catch (TimeoutException e) {
// no-op
}
}
}
else {
BlockingQueueConsumer.this.queue.put(new Delivery(consumerTag, envelope, properties, body));
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@SuppressWarnings("serial")
private static final class DeclarationException extends AmqpException {
DeclarationException() {
super("Failed to declare queue(s):");
}
private DeclarationException(Throwable t) {
super("Failed to declare queue(s):", t);
}
private final List<String> failedQueues = new ArrayList<String>();
private void addFailedQueue(String queue) {
this.failedQueues.add(queue);
}
private List<String> getFailedQueues() {
return this.failedQueues;
}
@Override
public String getMessage() {
return super.getMessage() + this.failedQueues.toString();
}
}
}