org.springframework.amqp.rabbit.core.RabbitTemplate.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.core.RabbitTemplate.java

Source

/*
 * Copyright 2002-2019 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
 *
 *      https://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.core;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.amqp.AmqpConnectException;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.AmqpIOException;
import org.springframework.amqp.AmqpIllegalStateException;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Address;
import org.springframework.amqp.core.AmqpMessageReturnedException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.ReceiveAndReplyCallback;
import org.springframework.amqp.core.ReceiveAndReplyMessageCallback;
import org.springframework.amqp.core.ReplyToAddressCallback;
import org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ChannelProxy;
import org.springframework.amqp.rabbit.connection.ClosingRecoveryListener;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.connection.PendingConfirm;
import org.springframework.amqp.rabbit.connection.PublisherCallbackChannel;
import org.springframework.amqp.rabbit.connection.RabbitAccessor;
import org.springframework.amqp.rabbit.connection.RabbitResourceHolder;
import org.springframework.amqp.rabbit.connection.RabbitUtils;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.DirectReplyToMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.DirectReplyToMessageListenerContainer.ChannelHolder;
import org.springframework.amqp.rabbit.support.ConsumerCancelledException;
import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.Delivery;
import org.springframework.amqp.rabbit.support.ListenerContainerAware;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.RabbitExceptionTranslator;
import org.springframework.amqp.rabbit.support.ValueExpression;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.amqp.support.converter.SmartMessageConverter;
import org.springframework.amqp.support.postprocessor.MessagePostProcessorUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.Lifecycle;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
import org.springframework.util.StringUtils;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.GetResponse;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;

/**
 * <p>
 * Helper class that simplifies synchronous RabbitMQ access (sending and receiving messages).
 * </p>
 *
 * <p>
 * The default settings are for non-transactional messaging, which reduces the amount of data exchanged with the broker.
 * To use a new transaction for every send or receive set the {@link #setChannelTransacted(boolean) channelTransacted}
 * flag. To extend the transaction over multiple invocations (more efficient), you can use a Spring transaction to
 * bracket the calls (with <code>channelTransacted=true</code> as well).
 * </p>
 *
 * <p>
 * The only mandatory property is the {@link #setConnectionFactory(ConnectionFactory) ConnectionFactory}. There are
 * strategies available for converting messages to and from Java objects (
 * {@link #setMessageConverter(MessageConverter) MessageConverter}) and for converting message headers (known as message
 * properties in AMQP, see {@link #setMessagePropertiesConverter(MessagePropertiesConverter) MessagePropertiesConverter}
 * ). The defaults probably do something sensible for typical use cases, as long as the message content-type is set
 * appropriately.
 * </p>
 *
 * <p>
 * The "send" methods all have overloaded versions that allow you to explicitly target an exchange and a routing key, or
 * you can set default values to be used in all send operations. The plain "receive" methods allow you to explicitly
 * target a queue to receive from, or you can set a default value for the template that applies to all explicit
 * receives. The convenience methods for send <b>and</b> receive use the sender defaults if no exchange or routing key
 * is specified, but they always use a temporary queue for the receive leg, so the default queue is ignored.
 * </p>
 *
 * @author Mark Pollack
 * @author Mark Fisher
 * @author Dave Syer
 * @author Gary Russell
 * @author Artem Bilan
 * @author Ernest Sadykov
 * @author Mark Norkin
 * @author Mohammad Hewedy
 *
 * @since 1.0
 */
public class RabbitTemplate extends RabbitAccessor // NOSONAR type line count
        implements BeanFactoryAware, RabbitOperations, MessageListener, ListenerContainerAware,
        PublisherCallbackChannel.Listener, Lifecycle, BeanNameAware {

    private static final String UNCHECKED = "unchecked";

    private static final String RETURN_CORRELATION_KEY = "spring_request_return_correlation";

    /** Alias for amq.direct default exchange. */
    private static final String DEFAULT_EXCHANGE = "";

    private static final String DEFAULT_ROUTING_KEY = "";

    private static final long DEFAULT_REPLY_TIMEOUT = 5000;

    private static final long DEFAULT_CONSUME_TIMEOUT = 10000;

    private static final String DEFAULT_ENCODING = "UTF-8";

    private static final SpelExpressionParser PARSER = new SpelExpressionParser();

    /*
     * Not static as normal since we want this TL to be scoped within the template instance.
     */
    private final ThreadLocal<Channel> dedicatedChannels = new ThreadLocal<>();

    private final AtomicInteger activeTemplateCallbacks = new AtomicInteger();

    private final ConcurrentMap<Channel, RabbitTemplate> publisherConfirmChannels = new ConcurrentHashMap<Channel, RabbitTemplate>();

    private final Map<String, PendingReply> replyHolder = new ConcurrentHashMap<String, PendingReply>();

    private final String uuid = UUID.randomUUID().toString();

    private final AtomicInteger messageTagProvider = new AtomicInteger();

    private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();

    private final ReplyToAddressCallback<?> defaultReplyToAddressCallback = (request,
            reply) -> getReplyToAddress(request);

    private final Map<ConnectionFactory, DirectReplyToMessageListenerContainer> directReplyToContainers = new HashMap<>();

    private final AtomicInteger containerInstance = new AtomicInteger();

    private String exchange = DEFAULT_EXCHANGE;

    private String routingKey = DEFAULT_ROUTING_KEY;

    // The default queue name that will be used for synchronous receives.
    private String defaultReceiveQueue;

    private long receiveTimeout = 0;

    private long replyTimeout = DEFAULT_REPLY_TIMEOUT;

    private MessageConverter messageConverter = new SimpleMessageConverter();

    private MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter();

    private String encoding = DEFAULT_ENCODING;

    private String replyAddress;

    @Nullable
    private ConfirmCallback confirmCallback;

    private ReturnCallback returnCallback;

    private Expression mandatoryExpression = new ValueExpression<Boolean>(false);

    private String correlationKey = null;

    private RetryTemplate retryTemplate;

    private RecoveryCallback<?> recoveryCallback;

    private Expression sendConnectionFactorySelectorExpression;

    private Expression receiveConnectionFactorySelectorExpression;

    private boolean useDirectReplyToContainer = true;

    private boolean useTemporaryReplyQueues;

    private Collection<MessagePostProcessor> beforePublishPostProcessors;

    private Collection<MessagePostProcessor> afterReceivePostProcessors;

    private CorrelationDataPostProcessor correlationDataPostProcessor;

    private Expression userIdExpression;

    private String beanName = "rabbitTemplate";

    private Executor taskExecutor;

    private boolean userCorrelationId;

    private boolean usePublisherConnection;

    private boolean noLocalReplyConsumer;

    private ErrorHandler replyErrorHandler;

    private volatile Boolean confirmsOrReturnsCapable;

    private volatile boolean publisherConfirms;

    private volatile boolean usingFastReplyTo;

    private volatile boolean evaluatedFastReplyTo;

    private volatile boolean isListener;

    /**
     * Convenient constructor for use with setter injection. Don't forget to set the connection factory.
     */
    public RabbitTemplate() {
        initDefaultStrategies(); // NOSONAR - intentionally overridable; other assertions will check

    }

    /**
     * Create a rabbit template with default strategies and settings.
     *
     * @param connectionFactory the connection factory to use
     */
    public RabbitTemplate(ConnectionFactory connectionFactory) {
        setConnectionFactory(connectionFactory);
    }

    /**
     * Set up the default strategies. Subclasses can override if necessary.
     */
    protected void initDefaultStrategies() {
        setMessageConverter(new SimpleMessageConverter());
    }

    /**
     * The name of the default exchange to use for send operations when none is specified. Defaults to <code>""</code>
     * which is the default exchange in the broker (per the AMQP specification).
     *
     * @param exchange the exchange name to use for send operations
     */
    public void setExchange(@Nullable String exchange) {
        this.exchange = (exchange != null) ? exchange : DEFAULT_EXCHANGE;
    }

    /**
     * @return the name of the default exchange used by this template.
     *
     * @since 1.6
     */
    public String getExchange() {
        return this.exchange;
    }

    /**
     * The value of a default routing key to use for send operations when none is specified. Default is empty which is
     * not helpful when using the default (or any direct) exchange, but fine if the exchange is a headers exchange for
     * instance.
     *
     * @param routingKey the default routing key to use for send operations
     */
    public void setRoutingKey(String routingKey) {
        this.routingKey = routingKey;
    }

    /**
     * @return the default routing key used by this template.
     *
     * @since 1.6
     */
    public String getRoutingKey() {
        return this.routingKey;
    }

    /**
     * The name of the default queue to receive messages from when none is specified explicitly.
     * @param queue the default queue name to use for receive
     * @since 2.1.2
     */
    public void setDefaultReceiveQueue(String queue) {
        this.defaultReceiveQueue = queue;
    }

    /**
     * The encoding to use when converting between byte arrays and Strings in message properties.
     *
     * @param encoding the encoding to set
     */
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    /**
     * The encoding used when converting between byte arrays and Strings in message properties.
     * @return the encoding.
     */
    public String getEncoding() {
        return this.encoding;
    }

    /**
     * An address for replies; if not provided, a temporary exclusive, auto-delete queue will
     * be used for each reply, unless RabbitMQ supports 'amq.rabbitmq.reply-to' - see
     * https://www.rabbitmq.com/direct-reply-to.html
     * <p>The address can be a simple queue name (in which case the reply will be routed via the default
     * exchange), or with the form {@code exchange/routingKey} to route the reply using an explicit
     * exchange and routing key.
     * @param replyAddress the replyAddress to set
     */
    public synchronized void setReplyAddress(String replyAddress) {
        this.replyAddress = replyAddress;
        this.evaluatedFastReplyTo = false;
    }

    /**
     * Specify the receive timeout in milliseconds when using {@code receive()} methods (for {@code sendAndReceive()}
     * methods, refer to {@link #setReplyTimeout(long) replyTimeout}. By default, the value is zero, which
     * means the {@code receive()} methods will return {@code null} immediately if there is no message
     * available. Set to less than zero to wait for a message indefinitely.
     * @param receiveTimeout the timeout.
     * @since 1.5
     */
    public void setReceiveTimeout(long receiveTimeout) {
        this.receiveTimeout = receiveTimeout;
    }

    /**
     * Specify the timeout in milliseconds to be used when waiting for a reply Message when using one of the
     * sendAndReceive methods. The default value is defined as {@link #DEFAULT_REPLY_TIMEOUT}. A negative value
     * indicates an indefinite timeout. Not used in the plain receive methods because there is no blocking receive
     * operation defined in the protocol.
     *
     * @param replyTimeout the reply timeout in milliseconds
     *
     * @see #sendAndReceive(String, String, Message)
     * @see #convertSendAndReceive(String, String, Object)
     */
    public void setReplyTimeout(long replyTimeout) {
        this.replyTimeout = replyTimeout;
    }

    /**
     * Set the message converter for this template. Used to resolve Object parameters to convertAndSend methods and
     * Object results from receiveAndConvert methods.
     * <p>
     * The default converter is a SimpleMessageConverter, which is able to handle byte arrays, Strings, and Serializable
     * Objects depending on the message content type header.
     *
     * @param messageConverter The message converter.
     *
     * @see #convertAndSend
     * @see #receiveAndConvert
     * @see org.springframework.amqp.support.converter.SimpleMessageConverter
     */
    public void setMessageConverter(MessageConverter messageConverter) {
        this.messageConverter = messageConverter;
    }

    /**
     * Set the {@link MessagePropertiesConverter} for this template. This converter is used to convert between raw byte
     * content in the message headers and plain Java objects. In particular there are limitations when dealing with very
     * long string headers, which hopefully are rare in practice, but if you need to use long headers you might need to
     * inject a special converter here.
     *
     * @param messagePropertiesConverter The message properties converter.
     */
    public void setMessagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) {
        Assert.notNull(messagePropertiesConverter, "messagePropertiesConverter must not be null");
        this.messagePropertiesConverter = messagePropertiesConverter;
    }

    /**
     * Return the properties converter.
     * @return the converter.
     * @since 2.0
     */
    protected MessagePropertiesConverter getMessagePropertiesConverter() {
        return this.messagePropertiesConverter;
    }

    /**
     * Return the message converter for this template. Useful for clients that want to take advantage of the converter
     * in {@link ChannelCallback} implementations.
     *
     * @return The message converter.
     */
    public MessageConverter getMessageConverter() {
        return this.messageConverter;
    }

    public void setConfirmCallback(ConfirmCallback confirmCallback) {
        Assert.state(this.confirmCallback == null || this.confirmCallback == confirmCallback,
                "Only one ConfirmCallback is supported by each RabbitTemplate");
        this.confirmCallback = confirmCallback;
    }

    public void setReturnCallback(ReturnCallback returnCallback) {
        Assert.state(this.returnCallback == null || this.returnCallback == returnCallback,
                "Only one ReturnCallback is supported by each RabbitTemplate");
        this.returnCallback = returnCallback;
    }

    /**
     * Set the mandatory flag when sending messages; only applies if a
     * {@link #setReturnCallback(ReturnCallback) returnCallback} had been provided.
     * @param mandatory the mandatory to set.
     */
    public void setMandatory(boolean mandatory) {
        this.mandatoryExpression = new ValueExpression<>(mandatory);
    }

    /**
     * @param mandatoryExpression a SpEL {@link Expression} to evaluate against each
     * request message, if a {@link #setReturnCallback(ReturnCallback) returnCallback} has
     * been provided. The result of the evaluation must be a {@code boolean} value.
     * @since 1.4
     */
    public void setMandatoryExpression(Expression mandatoryExpression) {
        Assert.notNull(mandatoryExpression, "'mandatoryExpression' must not be null");
        this.mandatoryExpression = mandatoryExpression;
    }

    /**
     * @param mandatoryExpression a SpEL {@link Expression} to evaluate against each
     * request message, if a {@link #setReturnCallback(ReturnCallback) returnCallback} has
     * been provided. The result of the evaluation must be a {@code boolean} value.
     * @since 2.0
     */
    public void setMandatoryExpressionString(String mandatoryExpression) {
        Assert.notNull(mandatoryExpression, "'mandatoryExpression' must not be null");
        this.mandatoryExpression = PARSER.parseExpression(mandatoryExpression);
    }

    /**
     * A SpEL {@link Expression} to evaluate
     * against each request message, if the provided {@link #getConnectionFactory()}
     * is an instance of {@link AbstractRoutingConnectionFactory}.
     * <p>
     * The result of this expression is used as {@code lookupKey} to get the target
     * {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory}
     * directly.
     * <p>
     * If this expression is evaluated to {@code null}, we fallback to the normal
     * {@link AbstractRoutingConnectionFactory} logic.
     * <p>
     * If there is no target {@link ConnectionFactory} with the evaluated {@code lookupKey},
     * we fallback to the normal {@link AbstractRoutingConnectionFactory} logic
     * only if its property {@code lenientFallback == true}.
     * <p>
     *  This expression is used for {@code send} operations.
     * @param sendConnectionFactorySelectorExpression a SpEL {@link Expression} to evaluate
     * @since 1.4
     */
    public void setSendConnectionFactorySelectorExpression(Expression sendConnectionFactorySelectorExpression) {
        this.sendConnectionFactorySelectorExpression = sendConnectionFactorySelectorExpression;
    }

    /**
     * A SpEL {@link Expression} to evaluate
     * against each {@code receive} {@code queueName}, if the provided {@link #getConnectionFactory()}
     * is an instance of {@link AbstractRoutingConnectionFactory}.
     * <p>
     * The result of this expression is used as {@code lookupKey} to get the target
     * {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory}
     * directly.
     * <p>
     * If this expression is evaluated to {@code null}, we fallback to the normal
     * {@link AbstractRoutingConnectionFactory} logic.
     * <p>
     * If there is no target {@link ConnectionFactory} with the evaluated {@code lookupKey},
     * we fallback to the normal {@link AbstractRoutingConnectionFactory} logic
     * only if its property {@code lenientFallback == true}.
     * <p>
     *  This expression is used for {@code receive} operations.
     * @param receiveConnectionFactorySelectorExpression a SpEL {@link Expression} to evaluate
     * @since 1.4
     */
    public void setReceiveConnectionFactorySelectorExpression(
            Expression receiveConnectionFactorySelectorExpression) {
        this.receiveConnectionFactorySelectorExpression = receiveConnectionFactorySelectorExpression;
    }

    /**
     * If set to 'correlationId' (default) the correlationId property
     * will be used; otherwise the supplied key will be used.
     * @param correlationKey the correlationKey to set
     */
    public void setCorrelationKey(String correlationKey) {
        Assert.hasText(correlationKey, "'correlationKey' must not be null or empty");
        if (!correlationKey.trim().equals("correlationId")) {
            this.correlationKey = correlationKey.trim();
        }
    }

    /**
     * Add a {@link RetryTemplate} which will be used for all rabbit operations.
     * @param retryTemplate The retry template.
     */
    public void setRetryTemplate(RetryTemplate retryTemplate) {
        this.retryTemplate = retryTemplate;
    }

    /**
     * Add a {@link RecoveryCallback} which is used for the {@code retryTemplate.execute}.
     * If {@link #retryTemplate} isn't provided {@link #recoveryCallback} is ignored.
     * {@link RecoveryCallback} should produce result compatible with
     * {@link #execute(ChannelCallback)} return type.
     * @param recoveryCallback The retry recoveryCallback.
     * @since 1.4
     */
    public void setRecoveryCallback(RecoveryCallback<?> recoveryCallback) {
        this.recoveryCallback = recoveryCallback;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
        this.evaluationContext.addPropertyAccessor(new MapAccessor());
    }

    /**
     * Set {@link MessagePostProcessor}s that will be invoked immediately before invoking
     * {@code Channel#basicPublish()}, after all other processing, except creating the
     * {@link BasicProperties} from {@link MessageProperties}. May be used for operations
     * such as compression. Processors are invoked in order, depending on {@code PriorityOrder},
     * {@code Order} and finally unordered.
     * @param beforePublishPostProcessors the post processor.
     * @since 1.4.2
     * @see #addBeforePublishPostProcessors(MessagePostProcessor...)
     */
    public void setBeforePublishPostProcessors(MessagePostProcessor... beforePublishPostProcessors) {
        Assert.notNull(beforePublishPostProcessors, "'beforePublishPostProcessors' cannot be null");
        Assert.noNullElements(beforePublishPostProcessors,
                "'beforePublishPostProcessors' cannot have null elements");
        this.beforePublishPostProcessors = MessagePostProcessorUtils
                .sort(Arrays.asList(beforePublishPostProcessors));
    }

    /**
     * Add {@link MessagePostProcessor} that will be invoked immediately before invoking
     * {@code Channel#basicPublish()}, after all other processing, except creating the
     * {@link BasicProperties} from {@link MessageProperties}. May be used for operations
     * such as compression. Processors are invoked in order, depending on {@code PriorityOrder},
     * {@code Order} and finally unordered.
     * <p>
     * In contrast to {@link #setBeforePublishPostProcessors(MessagePostProcessor...)}, this
     * method does not override the previously added beforePublishPostProcessors.
     * @param beforePublishPostProcessors the post processor.
     * @since 2.1.4
     */
    public void addBeforePublishPostProcessors(MessagePostProcessor... beforePublishPostProcessors) {
        Assert.notNull(beforePublishPostProcessors, "'beforePublishPostProcessors' cannot be null");
        if (this.beforePublishPostProcessors == null) {
            this.beforePublishPostProcessors = new ArrayList<>();
        }
        this.beforePublishPostProcessors.addAll(Arrays.asList(beforePublishPostProcessors));
        this.beforePublishPostProcessors = MessagePostProcessorUtils.sort(this.beforePublishPostProcessors);
    }

    /**
     * Remove the provided {@link MessagePostProcessor} from the {@link #beforePublishPostProcessors} list.
     * @param beforePublishPostProcessor the MessagePostProcessor to remove.
     * @return the boolean if the provided post processor has been removed.
     * @since 2.1.4
     * @see #addBeforePublishPostProcessors(MessagePostProcessor...)
     */
    public boolean removeBeforePublishPostProcessor(MessagePostProcessor beforePublishPostProcessor) {
        Assert.notNull(beforePublishPostProcessor, "'beforePublishPostProcessor' cannot be null");
        if (this.beforePublishPostProcessors != null) {
            return this.beforePublishPostProcessors.remove(beforePublishPostProcessor);
        }
        return false;
    }

    /**
     * Set a {@link MessagePostProcessor} that will be invoked immediately after a {@code Channel#basicGet()}
     * and before any message conversion is performed.
     * May be used for operations such as decompression. Processors are invoked in order,
     * depending on {@code PriorityOrder}, {@code Order} and finally unordered.
     * @param afterReceivePostProcessors the post processor.
     * @since 1.5
     * @see #addAfterReceivePostProcessors(MessagePostProcessor...)
     */
    public void setAfterReceivePostProcessors(MessagePostProcessor... afterReceivePostProcessors) {
        Assert.notNull(afterReceivePostProcessors, "'afterReceivePostProcessors' cannot be null");
        Assert.noNullElements(afterReceivePostProcessors, "'afterReceivePostProcessors' cannot have null elements");
        this.afterReceivePostProcessors = MessagePostProcessorUtils.sort(Arrays.asList(afterReceivePostProcessors));
    }

    /**
     * Return configured after receive {@link MessagePostProcessor}s or {@code null}.
     * @return configured after receive {@link MessagePostProcessor}s or {@code null}.
     * @since 2.1.5
     */
    @Nullable
    public Collection<MessagePostProcessor> getAfterReceivePostProcessors() {
        return this.afterReceivePostProcessors != null
                ? Collections.unmodifiableCollection(this.afterReceivePostProcessors)
                : null;
    }

    /**
     * Add {@link MessagePostProcessor} that will be invoked immediately after a {@code Channel#basicGet()}
     * and before any message conversion is performed.
     * May be used for operations such as decompression. Processors are invoked in order,
     * depending on {@code PriorityOrder}, {@code Order} and finally unordered.
     * <p>
     * In contrast to {@link #setAfterReceivePostProcessors(MessagePostProcessor...)}, this
     * method does not override the previously added afterReceivePostProcessors.
     * @param afterReceivePostProcessors the post processor.
     * @since 2.1.4
     */
    public void addAfterReceivePostProcessors(MessagePostProcessor... afterReceivePostProcessors) {
        Assert.notNull(afterReceivePostProcessors, "'afterReceivePostProcessors' cannot be null");
        if (this.afterReceivePostProcessors == null) {
            this.afterReceivePostProcessors = new ArrayList<>();
        }
        this.afterReceivePostProcessors.addAll(Arrays.asList(afterReceivePostProcessors));
        this.afterReceivePostProcessors = MessagePostProcessorUtils.sort(this.afterReceivePostProcessors);
    }

    /**
     * Remove the provided {@link MessagePostProcessor} from the {@link #afterReceivePostProcessors} list.
     * @param afterReceivePostProcessor the MessagePostProcessor to remove.
     * @return the boolean if the provided post processor has been removed.
     * @since 2.1.4
     * @see #addAfterReceivePostProcessors(MessagePostProcessor...)
     */
    public boolean removeAfterReceivePostProcessor(MessagePostProcessor afterReceivePostProcessor) {
        Assert.notNull(afterReceivePostProcessor, "'afterReceivePostProcessor' cannot be null");
        if (this.afterReceivePostProcessors != null) {
            return this.afterReceivePostProcessors.remove(afterReceivePostProcessor);
        }
        return false;
    }

    /**
     * Set a {@link CorrelationDataPostProcessor} to be invoked before publishing a message.
     * Correlation data is used to correlate publisher confirms.
     * @param correlationDataPostProcessor the post processor.
     * @since 1.6.7
     * @see #setConfirmCallback(ConfirmCallback)
     */
    public void setCorrelationDataPostProcessor(CorrelationDataPostProcessor correlationDataPostProcessor) {
        this.correlationDataPostProcessor = correlationDataPostProcessor;
    }

    /**
     * By default, when the broker supports it and no
     * {@link #setReplyAddress(String) replyAddress} is provided, send/receive
     * methods will use Direct reply-to (https://www.rabbitmq.com/direct-reply-to.html).
     * Setting this property to true will override that behavior and use
     * a temporary, auto-delete, queue for each request instead.
     * Changing this property has no effect once the first request has been
     * processed.
     * @param value true to use temporary queues.
     * @since 1.6
     */
    public void setUseTemporaryReplyQueues(boolean value) {
        this.useTemporaryReplyQueues = value;
    }

    /**
     * Set whether or not to use a {@link DirectReplyToMessageListenerContainer} when
     * direct reply-to is available and being used. When false, a new consumer is created
     * for each request (the mechanism used in versions prior to 2.0). Default true.
     * @param useDirectReplyToContainer set to false to use a consumer per request.
     * @since 2.0
     * @see #setUseTemporaryReplyQueues(boolean)
     */
    public void setUseDirectReplyToContainer(boolean useDirectReplyToContainer) {
        this.useDirectReplyToContainer = useDirectReplyToContainer;
    }

    /**
     * Set an expression to be evaluated to set the userId message property if it
     * evaluates to a non-null value and the property is not already set in the
     * message to be sent.
     * See https://www.rabbitmq.com/validated-user-id.html
     * @param userIdExpression the expression.
     * @since 1.6
     */
    public void setUserIdExpression(Expression userIdExpression) {
        this.userIdExpression = userIdExpression;
    }

    /**
     * Set an expression to be evaluated to set the userId message property if it
     * evaluates to a non-null value and the property is not already set in the
     * message to be sent.
     * See https://www.rabbitmq.com/validated-user-id.html
     * @param userIdExpression the expression.
     * @since 1.6
     */
    public void setUserIdExpressionString(String userIdExpression) {
        this.userIdExpression = PARSER.parseExpression(userIdExpression);
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    /**
     * Set a task executor to use when using a {@link DirectReplyToMessageListenerContainer}.
     * @param taskExecutor the executor.
     * @since 2.0
     */
    public void setTaskExecutor(Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    /**
     * Set to true to use correlation id provided by the message instead of generating
     * the correlation id for request/reply scenarios. The correlation id must be unique
     * for all in-process requests to avoid cross talk.
     * <p>
     * <b>Users must therefore take create care to ensure uniqueness.</b>
     * @param userCorrelationId true to use user correlation data.
     */
    public void setUserCorrelationId(boolean userCorrelationId) {
        this.userCorrelationId = userCorrelationId;
    }

    /**
     * True if separate publisher connection(s) are used.
     * @return true or false.
     * @since 2.0.2
     * @see #setUsePublisherConnection(boolean)
     */
    public boolean isUsePublisherConnection() {
        return this.usePublisherConnection;
    }

    /**
     * To avoid deadlocked connections, it is generally recommended to use
     * a separate connection for publishers and consumers (except when a publisher
     * is participating in a consumer transaction). Default 'false'; will change
     * to 'true' in 2.1.
     * @param usePublisherConnection true to use a publisher connection.
     * @since 2.0.2
     */
    public void setUsePublisherConnection(boolean usePublisherConnection) {
        this.usePublisherConnection = usePublisherConnection;
    }

    /**
     * Set to true for a no-local consumer. Defaults to false.
     * @param noLocalReplyConsumer true for a no-local consumer.
     * @since 2.1
     * @see AbstractMessageListenerContainer#setNoLocal(boolean)
     * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, com.rabbitmq.client.Consumer)
     */
    public void setNoLocalReplyConsumer(boolean noLocalReplyConsumer) {
        this.noLocalReplyConsumer = noLocalReplyConsumer;
    }

    /**
     * When using a direct reply-to container for request/reply operations, set an error
     * handler to be invoked when a reply delivery fails (e.g. due to a late reply).
     * @param replyErrorHandler the reply error handler
     * @since 2.0.11
     * @see #setUseDirectReplyToContainer(boolean)
     */
    public void setReplyErrorHandler(ErrorHandler replyErrorHandler) {
        this.replyErrorHandler = replyErrorHandler;
    }

    /**
     * Invoked by the container during startup so it can verify the queue is correctly
     * configured (if a simple reply queue name is used instead of exchange/routingKey).
     * @return the queue name, if configured.
     * @since 1.5
     */
    @Override
    @Nullable
    public Collection<String> expectedQueueNames() {
        this.isListener = true;
        Collection<String> replyQueue = null;
        if (this.replyAddress == null || this.replyAddress.equals(Address.AMQ_RABBITMQ_REPLY_TO)) {
            throw new IllegalStateException("A listener container must not be provided when using direct reply-to");
        } else {
            Address address = new Address(this.replyAddress);
            if ("".equals(address.getExchangeName())) {
                replyQueue = Collections.singletonList(address.getRoutingKey());
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("Cannot verify reply queue because 'replyAddress' is not a simple queue name: "
                            + this.replyAddress);
                }
            }
        }
        return replyQueue;
    }

    /**
     * Gets unconfirmed correlation data older than age and removes them.
     * @param age in milliseconds
     * @return the collection of correlation data for which confirms have
     * not been received or null if no such confirms exist.
     */
    @Nullable
    public Collection<CorrelationData> getUnconfirmed(long age) {
        Set<CorrelationData> unconfirmed = new HashSet<>();
        long cutoffTime = System.currentTimeMillis() - age;
        for (Channel channel : this.publisherConfirmChannels.keySet()) {
            Collection<PendingConfirm> confirms = ((PublisherCallbackChannel) channel).expire(this, cutoffTime);
            for (PendingConfirm confirm : confirms) {
                unconfirmed.add(confirm.getCorrelationData());
            }
        }
        return unconfirmed.size() > 0 ? unconfirmed : null;
    }

    /**
     * Gets unconfirmed messages count.
     * @return The count of the messages that are not confirmed yet by RabbitMQ.
     * @since 2.0
     */
    public int getUnconfirmedCount() {
        return this.publisherConfirmChannels.keySet().stream()
                .mapToInt(channel -> ((PublisherCallbackChannel) channel).getPendingConfirmsCount(this)).sum();
    }

    @Override
    public final void start() {
        doStart();
    }

    /**
     * Perform additional start actions.
     * @since 2.0
     */
    protected void doStart() {
        // NOSONAR
    }

    @Override
    public final void stop() {
        synchronized (this.directReplyToContainers) {
            this.directReplyToContainers.values().stream().filter(AbstractMessageListenerContainer::isRunning)
                    .forEach(AbstractMessageListenerContainer::stop);
            this.directReplyToContainers.clear();
        }
        doStop();
    }

    /**
     * Perform additional stop actions.
     * @since 2.0
     */
    protected void doStop() {
        // NOSONAR
    }

    @Override
    public boolean isRunning() {
        synchronized (this.directReplyToContainers) {
            return this.directReplyToContainers.values().stream()
                    .anyMatch(AbstractMessageListenerContainer::isRunning);
        }
    }

    private void evaluateFastReplyTo() {
        this.usingFastReplyTo = useDirectReplyTo();
        this.evaluatedFastReplyTo = true;
    }

    /**
     * Override this method use some other criteria to decide whether or not to use
     * direct reply-to (https://www.rabbitmq.com/direct-reply-to.html).
     * The default implementation returns true if the broker supports it and there
     * is no {@link #setReplyAddress(String) replyAddress} set and
     * {@link #setUseTemporaryReplyQueues(boolean) useTemporaryReplyQueues} is false.
     * When direct reply-to is not used, the template
     * will create a temporary, exclusive, auto-delete queue for the reply.
     * <p>
     * This method is invoked once only - when the first message is sent, from a
     * synchronized block.
     * @return true to use direct reply-to.
     */
    protected boolean useDirectReplyTo() {
        if (this.useTemporaryReplyQueues) {
            if (this.replyAddress != null) {
                logger.warn("'useTemporaryReplyQueues' is ignored when a 'replyAddress' is provided");
            } else {
                return false;
            }
        }
        if (this.replyAddress == null || Address.AMQ_RABBITMQ_REPLY_TO.equals(this.replyAddress)) {
            try {
                return execute(channel -> { // NOSONAR - never null
                    channel.queueDeclarePassive(Address.AMQ_RABBITMQ_REPLY_TO);
                    return true;
                });
            } catch (AmqpConnectException | AmqpIOException ex) {
                if (shouldRethrow(ex)) {
                    throw ex;
                }
            }
        }
        return false;
    }

    private boolean shouldRethrow(AmqpException ex) {
        Throwable cause = ex;
        while (cause != null && !(cause instanceof ShutdownSignalException)) {
            cause = cause.getCause();
        }
        if (cause != null && RabbitUtils.isPassiveDeclarationChannelClose((ShutdownSignalException) cause)) {
            if (logger.isWarnEnabled()) {
                logger.warn("Broker does not support fast replies via 'amq.rabbitmq.reply-to', temporary "
                        + "queues will be used: " + cause.getMessage() + ".");
            }
            this.replyAddress = null;
            return false;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("IO error, deferring directReplyTo detection: " + ex.toString());
        }
        return true;
    }

    @Override
    public void send(Message message) throws AmqpException {
        send(this.exchange, this.routingKey, message);
    }

    @Override
    public void send(String routingKey, Message message) throws AmqpException {
        send(this.exchange, routingKey, message);
    }

    @Override
    public void send(final String exchange, final String routingKey, final Message message) throws AmqpException {
        send(exchange, routingKey, message, null);
    }

    @Override
    public void send(final String exchange, final String routingKey, final Message message,
            @Nullable final CorrelationData correlationData) throws AmqpException {
        execute(channel -> {
            doSend(channel, exchange, routingKey, message,
                    (RabbitTemplate.this.returnCallback != null
                            || (correlationData != null && StringUtils.hasText(correlationData.getId())))
                            && RabbitTemplate.this.mandatoryExpression
                                    .getValue(RabbitTemplate.this.evaluationContext, message, Boolean.class),
                    correlationData);
            return null;
        }, obtainTargetConnectionFactory(this.sendConnectionFactorySelectorExpression, message));
    }

    private ConnectionFactory obtainTargetConnectionFactory(Expression expression, Object rootObject) {
        if (expression != null && getConnectionFactory() instanceof AbstractRoutingConnectionFactory) {
            AbstractRoutingConnectionFactory routingConnectionFactory = (AbstractRoutingConnectionFactory) getConnectionFactory();
            Object lookupKey;
            if (rootObject != null) {
                lookupKey = this.sendConnectionFactorySelectorExpression.getValue(this.evaluationContext,
                        rootObject);
            } else {
                lookupKey = this.sendConnectionFactorySelectorExpression.getValue(this.evaluationContext);
            }
            if (lookupKey != null) {
                ConnectionFactory connectionFactory = routingConnectionFactory
                        .getTargetConnectionFactory(lookupKey);
                if (connectionFactory != null) {
                    return connectionFactory;
                } else if (!routingConnectionFactory.isLenientFallback()) {
                    throw new IllegalStateException(
                            "Cannot determine target ConnectionFactory for lookup key [" + lookupKey + "]");
                }
            }
        }
        return getConnectionFactory();
    }

    @Override
    public void convertAndSend(Object object) throws AmqpException {
        convertAndSend(this.exchange, this.routingKey, object, (CorrelationData) null);
    }

    @Override
    public void correlationConvertAndSend(Object object, CorrelationData correlationData) throws AmqpException {
        convertAndSend(this.exchange, this.routingKey, object, correlationData);
    }

    @Override
    public void convertAndSend(String routingKey, final Object object) throws AmqpException {
        convertAndSend(this.exchange, routingKey, object, (CorrelationData) null);
    }

    @Override
    public void convertAndSend(String routingKey, final Object object, CorrelationData correlationData)
            throws AmqpException {
        convertAndSend(this.exchange, routingKey, object, correlationData);
    }

    @Override
    public void convertAndSend(String exchange, String routingKey, final Object object) throws AmqpException {
        convertAndSend(exchange, routingKey, object, (CorrelationData) null);
    }

    @Override
    public void convertAndSend(String exchange, String routingKey, final Object object,
            @Nullable CorrelationData correlationData) throws AmqpException {

        send(exchange, routingKey, convertMessageIfNecessary(object), correlationData);
    }

    @Override
    public void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) throws AmqpException {
        convertAndSend(this.exchange, this.routingKey, message, messagePostProcessor);
    }

    @Override
    public void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor)
            throws AmqpException {
        convertAndSend(this.exchange, routingKey, message, messagePostProcessor, null);
    }

    @Override
    public void convertAndSend(Object message, MessagePostProcessor messagePostProcessor,
            CorrelationData correlationData) throws AmqpException {
        convertAndSend(this.exchange, this.routingKey, message, messagePostProcessor, correlationData);
    }

    @Override
    public void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor,
            CorrelationData correlationData) throws AmqpException {
        convertAndSend(this.exchange, routingKey, message, messagePostProcessor, correlationData);
    }

    @Override
    public void convertAndSend(String exchange, String routingKey, final Object message,
            final MessagePostProcessor messagePostProcessor) throws AmqpException {
        convertAndSend(exchange, routingKey, message, messagePostProcessor, null);
    }

    @Override
    public void convertAndSend(String exchange, String routingKey, final Object message,
            final MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData)
            throws AmqpException {
        Message messageToSend = convertMessageIfNecessary(message);
        messageToSend = messagePostProcessor.postProcessMessage(messageToSend, correlationData);
        send(exchange, routingKey, messageToSend, correlationData);
    }

    @Override
    @Nullable
    public Message receive() throws AmqpException {
        return this.receive(getRequiredQueue());
    }

    @Override
    @Nullable
    public Message receive(String queueName) {
        if (this.receiveTimeout == 0) {
            return doReceiveNoWait(queueName);
        } else {
            return receive(queueName, this.receiveTimeout);
        }
    }

    /**
     * Non-blocking receive.
     * @param queueName the queue to receive from.
     * @return The message, or null if none immediately available.
     * @since 1.5
     */
    @Nullable
    protected Message doReceiveNoWait(final String queueName) {
        Message message = execute(channel -> {
            GetResponse response = channel.basicGet(queueName, !isChannelTransacted());
            // Response can be null is the case that there is no message on the queue.
            if (response != null) {
                long deliveryTag = response.getEnvelope().getDeliveryTag();
                if (isChannelLocallyTransacted(channel)) {
                    channel.basicAck(deliveryTag, false);
                    channel.txCommit();
                } else if (isChannelTransacted()) {
                    // Not locally transacted but it is transacted so it
                    // could be synchronized with an external transaction
                    ConnectionFactoryUtils.registerDeliveryTag(getConnectionFactory(), channel, deliveryTag);
                }

                return RabbitTemplate.this.buildMessageFromResponse(response);
            }
            return null;
        }, obtainTargetConnectionFactory(this.receiveConnectionFactorySelectorExpression, queueName));
        logReceived(message);
        return message;
    }

    @Override
    @Nullable
    public Message receive(long timeoutMillis) throws AmqpException {
        String queue = getRequiredQueue();
        if (timeoutMillis == 0) {
            return doReceiveNoWait(queue);
        } else {
            return receive(queue, timeoutMillis);
        }
    }

    @Override
    @Nullable
    public Message receive(final String queueName, final long timeoutMillis) {
        Message message = execute(channel -> {
            Delivery delivery = consumeDelivery(channel, queueName, timeoutMillis);
            if (delivery == null) {
                return null;
            } else {
                if (isChannelLocallyTransacted(channel)) {
                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                    channel.txCommit();
                } else if (isChannelTransacted()) {
                    ConnectionFactoryUtils.registerDeliveryTag(getConnectionFactory(), channel,
                            delivery.getEnvelope().getDeliveryTag());
                } else {
                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                }
                return buildMessageFromDelivery(delivery);
            }
        });
        logReceived(message);
        return message;
    }

    @Override
    @Nullable
    public Object receiveAndConvert() throws AmqpException {
        return receiveAndConvert(this.getRequiredQueue());
    }

    @Override
    @Nullable
    public Object receiveAndConvert(String queueName) throws AmqpException {
        return receiveAndConvert(queueName, this.receiveTimeout);
    }

    @Override
    @Nullable
    public Object receiveAndConvert(long timeoutMillis) throws AmqpException {
        return receiveAndConvert(this.getRequiredQueue(), timeoutMillis);
    }

    @Override
    @Nullable
    public Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException {
        Message response = timeoutMillis == 0 ? doReceiveNoWait(queueName) : receive(queueName, timeoutMillis);
        if (response != null) {
            return getRequiredMessageConverter().fromMessage(response);
        }
        return null;
    }

    @Override
    @Nullable
    public <T> T receiveAndConvert(ParameterizedTypeReference<T> type) throws AmqpException {
        return receiveAndConvert(this.getRequiredQueue(), type);
    }

    @Override
    @Nullable
    public <T> T receiveAndConvert(String queueName, ParameterizedTypeReference<T> type) throws AmqpException {
        return receiveAndConvert(queueName, this.receiveTimeout, type);
    }

    @Override
    @Nullable
    public <T> T receiveAndConvert(long timeoutMillis, ParameterizedTypeReference<T> type) throws AmqpException {
        return receiveAndConvert(this.getRequiredQueue(), timeoutMillis, type);
    }

    @Override
    @SuppressWarnings(UNCHECKED)
    @Nullable
    public <T> T receiveAndConvert(String queueName, long timeoutMillis, ParameterizedTypeReference<T> type)
            throws AmqpException {
        Message response = timeoutMillis == 0 ? doReceiveNoWait(queueName) : receive(queueName, timeoutMillis);
        if (response != null) {
            return (T) getRequiredSmartMessageConverter().fromMessage(response, type);
        }
        return null;
    }

    @Override
    public <R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback) throws AmqpException {
        return receiveAndReply(this.getRequiredQueue(), callback);
    }

    @Override
    @SuppressWarnings(UNCHECKED)
    public <R, S> boolean receiveAndReply(final String queueName, ReceiveAndReplyCallback<R, S> callback)
            throws AmqpException {
        return receiveAndReply(queueName, callback, (ReplyToAddressCallback<S>) this.defaultReplyToAddressCallback);

    }

    @Override
    public <R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback, final String exchange,
            final String routingKey) throws AmqpException {

        return receiveAndReply(this.getRequiredQueue(), callback, exchange, routingKey);
    }

    @Override
    public <R, S> boolean receiveAndReply(final String queueName, ReceiveAndReplyCallback<R, S> callback,
            final String replyExchange, final String replyRoutingKey) throws AmqpException {

        return receiveAndReply(queueName, callback,
                (request, reply) -> new Address(replyExchange, replyRoutingKey));
    }

    @Override
    public <R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback,
            ReplyToAddressCallback<S> replyToAddressCallback) throws AmqpException {

        return receiveAndReply(this.getRequiredQueue(), callback, replyToAddressCallback);
    }

    @Override
    public <R, S> boolean receiveAndReply(String queueName, ReceiveAndReplyCallback<R, S> callback,
            ReplyToAddressCallback<S> replyToAddressCallback) throws AmqpException {
        return doReceiveAndReply(queueName, callback, replyToAddressCallback);
    }

    private <R, S> boolean doReceiveAndReply(final String queueName, final ReceiveAndReplyCallback<R, S> callback,
            final ReplyToAddressCallback<S> replyToAddressCallback) throws AmqpException {

        Boolean result = execute(channel -> {
            Message receiveMessage = receiveForReply(queueName, channel);
            if (receiveMessage != null) {
                return sendReply(callback, replyToAddressCallback, channel, receiveMessage);
            }
            return false;
        }, obtainTargetConnectionFactory(this.receiveConnectionFactorySelectorExpression, queueName));
        return result == null ? false : result;
    }

    @Nullable
    private Message receiveForReply(final String queueName, Channel channel) throws IOException {
        boolean channelTransacted = isChannelTransacted();
        boolean channelLocallyTransacted = isChannelLocallyTransacted(channel);
        Message receiveMessage = null;
        if (RabbitTemplate.this.receiveTimeout == 0) {
            GetResponse response = channel.basicGet(queueName, !channelTransacted);
            // Response can be null in the case that there is no message on the queue.
            if (response != null) {
                long deliveryTag1 = response.getEnvelope().getDeliveryTag();

                if (channelLocallyTransacted) {
                    channel.basicAck(deliveryTag1, false);
                } else if (channelTransacted) {
                    // Not locally transacted but it is transacted so it could be
                    // synchronized with an external transaction
                    ConnectionFactoryUtils.registerDeliveryTag(getConnectionFactory(), channel, deliveryTag1);
                }
                receiveMessage = buildMessageFromResponse(response);
            }
        } else {
            Delivery delivery = consumeDelivery(channel, queueName, this.receiveTimeout);
            if (delivery != null) {
                long deliveryTag2 = delivery.getEnvelope().getDeliveryTag();
                if (channelTransacted && !channelLocallyTransacted) {
                    // Not locally transacted but it is transacted so it could be
                    // synchronized with an external transaction
                    ConnectionFactoryUtils.registerDeliveryTag(getConnectionFactory(), channel, deliveryTag2);
                } else {
                    channel.basicAck(deliveryTag2, false);
                }
                receiveMessage = buildMessageFromDelivery(delivery);
            }
        }
        logReceived(receiveMessage);
        return receiveMessage;
    }

    @Nullable // NOSONAR complexity
    private Delivery consumeDelivery(Channel channel, String queueName, long timeoutMillis) throws IOException {

        Delivery delivery = null;
        RuntimeException exception = null;
        CompletableFuture<Delivery> future = new CompletableFuture<>();
        ShutdownListener shutdownListener = c -> {
            if (!RabbitUtils.isNormalChannelClose(c)) {
                future.completeExceptionally(c);
            }
        };
        channel.addShutdownListener(shutdownListener);
        ClosingRecoveryListener.addRecoveryListenerIfNecessary(channel);
        DefaultConsumer consumer = null;
        try {
            consumer = createConsumer(queueName, channel, future,
                    timeoutMillis < 0 ? DEFAULT_CONSUME_TIMEOUT : timeoutMillis);
            if (timeoutMillis < 0) {
                delivery = future.get();
            } else {
                delivery = future.get(timeoutMillis, TimeUnit.MILLISECONDS);
            }
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            this.logger.error("Consumer failed to receive message: " + consumer, cause);
            exception = RabbitExceptionTranslator.convertRabbitAccessException(cause);
            throw exception;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (TimeoutException e) {
            RabbitUtils.setPhysicalCloseRequired(channel, true);
        } finally {
            if (consumer != null && !(exception instanceof ConsumerCancelledException) && channel.isOpen()) {
                cancelConsumerQuietly(channel, consumer);
            }
            try {
                channel.removeShutdownListener(shutdownListener);
            } catch (Exception e) {
                // NOSONAR - channel might have closed.
            }
        }
        return delivery;
    }

    private void logReceived(Message message) {
        if (message == null) {
            logger.debug("Received no message");
        } else if (logger.isDebugEnabled()) {
            logger.debug("Received: " + message);
        }
    }

    @SuppressWarnings(UNCHECKED)
    private <R, S> boolean sendReply(final ReceiveAndReplyCallback<R, S> callback,
            final ReplyToAddressCallback<S> replyToAddressCallback, Channel channel, Message receiveMessage)
            throws IOException {

        Object receive = receiveMessage;
        if (!(ReceiveAndReplyMessageCallback.class.isAssignableFrom(callback.getClass()))) {
            receive = getRequiredMessageConverter().fromMessage(receiveMessage);
        }

        S reply;
        try {
            reply = callback.handle((R) receive);
        } catch (ClassCastException e) {
            StackTraceElement[] trace = e.getStackTrace();
            if (trace[0].getMethodName().equals("handle") && trace[1].getFileName().equals("RabbitTemplate.java")) {
                throw new IllegalArgumentException(
                        "ReceiveAndReplyCallback '" + callback + "' can't handle received object '" + receive + "'",
                        e);
            } else {
                throw e;
            }
        }

        if (reply != null) {
            doSendReply(replyToAddressCallback, channel, receiveMessage, reply);
        } else if (isChannelLocallyTransacted(channel)) {
            channel.txCommit();
        }

        return true;
    }

    private <S> void doSendReply(final ReplyToAddressCallback<S> replyToAddressCallback, Channel channel,
            Message receiveMessage, S reply) throws IOException {

        Address replyTo = replyToAddressCallback.getReplyToAddress(receiveMessage, reply);

        Message replyMessage = convertMessageIfNecessary(reply);

        MessageProperties receiveMessageProperties = receiveMessage.getMessageProperties();
        MessageProperties replyMessageProperties = replyMessage.getMessageProperties();

        Object correlation = this.correlationKey == null ? receiveMessageProperties.getCorrelationId()
                : receiveMessageProperties.getHeaders().get(this.correlationKey);

        if (this.correlationKey == null || correlation == null) {
            // using standard correlationId property
            if (correlation == null) {
                String messageId = receiveMessageProperties.getMessageId();
                if (messageId != null) {
                    correlation = messageId;
                }
            }
            replyMessageProperties.setCorrelationId((String) correlation);
        } else {
            replyMessageProperties.setHeader(this.correlationKey, correlation);
        }

        // 'doSend()' takes care of 'channel.txCommit()'.
        doSend(channel, replyTo.getExchangeName(), replyTo.getRoutingKey(), replyMessage,
                RabbitTemplate.this.returnCallback != null && isMandatoryFor(replyMessage), null);
    }

    @Override
    @Nullable
    public Message sendAndReceive(final Message message) throws AmqpException {
        return sendAndReceive(message, null);
    }

    @Nullable
    public Message sendAndReceive(final Message message, @Nullable CorrelationData correlationData)
            throws AmqpException {

        return doSendAndReceive(this.exchange, this.routingKey, message, correlationData);
    }

    @Override
    @Nullable
    public Message sendAndReceive(final String routingKey, final Message message) throws AmqpException {
        return sendAndReceive(routingKey, message, null);
    }

    @Nullable
    public Message sendAndReceive(final String routingKey, final Message message,
            @Nullable CorrelationData correlationData) throws AmqpException {

        return doSendAndReceive(this.exchange, routingKey, message, correlationData);
    }

    @Override
    @Nullable
    public Message sendAndReceive(final String exchange, final String routingKey, final Message message)
            throws AmqpException {

        return sendAndReceive(exchange, routingKey, message, null);
    }

    @Nullable
    public Message sendAndReceive(final String exchange, final String routingKey, final Message message,
            @Nullable CorrelationData correlationData) throws AmqpException {

        return doSendAndReceive(exchange, routingKey, message, correlationData);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final Object message) throws AmqpException {
        return convertSendAndReceive(message, (CorrelationData) null);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final Object message, @Nullable CorrelationData correlationData)
            throws AmqpException {

        return convertSendAndReceive(this.exchange, this.routingKey, message, null, correlationData);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String routingKey, final Object message) throws AmqpException {
        return convertSendAndReceive(routingKey, message, (CorrelationData) null);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String routingKey, final Object message,
            @Nullable CorrelationData correlationData) throws AmqpException {

        return convertSendAndReceive(this.exchange, routingKey, message, null, correlationData);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message)
            throws AmqpException {

        return convertSendAndReceive(exchange, routingKey, message, (CorrelationData) null);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message,
            @Nullable CorrelationData correlationData) throws AmqpException {

        return convertSendAndReceive(exchange, routingKey, message, null, correlationData);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final Object message, final MessagePostProcessor messagePostProcessor)
            throws AmqpException {
        return convertSendAndReceive(message, messagePostProcessor, null);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final Object message, final MessagePostProcessor messagePostProcessor,
            @Nullable CorrelationData correlationData) throws AmqpException {

        return convertSendAndReceive(this.exchange, this.routingKey, message, messagePostProcessor,
                correlationData);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String routingKey, final Object message,
            final MessagePostProcessor messagePostProcessor) throws AmqpException {

        return convertSendAndReceive(routingKey, message, messagePostProcessor, null);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String routingKey, final Object message,
            final MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData)
            throws AmqpException {

        return convertSendAndReceive(this.exchange, routingKey, message, messagePostProcessor, correlationData);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message,
            final MessagePostProcessor messagePostProcessor) throws AmqpException {
        return convertSendAndReceive(exchange, routingKey, message, messagePostProcessor, null);
    }

    @Override
    @Nullable
    public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message,
            @Nullable final MessagePostProcessor messagePostProcessor,
            @Nullable final CorrelationData correlationData) throws AmqpException {

        Message replyMessage = convertSendAndReceiveRaw(exchange, routingKey, message, messagePostProcessor,
                correlationData);
        if (replyMessage == null) {
            return null;
        }
        return this.getRequiredMessageConverter().fromMessage(replyMessage);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final Object message, ParameterizedTypeReference<T> responseType)
            throws AmqpException {
        return convertSendAndReceiveAsType(message, (CorrelationData) null, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final Object message, @Nullable CorrelationData correlationData,
            ParameterizedTypeReference<T> responseType) throws AmqpException {

        return convertSendAndReceiveAsType(this.exchange, this.routingKey, message, null, correlationData,
                responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final String routingKey, final Object message,
            ParameterizedTypeReference<T> responseType) throws AmqpException {

        return convertSendAndReceiveAsType(routingKey, message, (CorrelationData) null, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final String routingKey, final Object message,
            @Nullable CorrelationData correlationData, ParameterizedTypeReference<T> responseType)
            throws AmqpException {

        return convertSendAndReceiveAsType(this.exchange, routingKey, message, null, correlationData, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final String exchange, final String routingKey, final Object message,
            ParameterizedTypeReference<T> responseType) throws AmqpException {

        return convertSendAndReceiveAsType(exchange, routingKey, message, (CorrelationData) null, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final Object message,
            @Nullable final MessagePostProcessor messagePostProcessor, ParameterizedTypeReference<T> responseType)
            throws AmqpException {

        return convertSendAndReceiveAsType(message, messagePostProcessor, null, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final Object message,
            @Nullable final MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData,
            ParameterizedTypeReference<T> responseType) throws AmqpException {

        return convertSendAndReceiveAsType(this.exchange, this.routingKey, message, messagePostProcessor,
                correlationData, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final String routingKey, final Object message,
            @Nullable final MessagePostProcessor messagePostProcessor, ParameterizedTypeReference<T> responseType)
            throws AmqpException {

        return convertSendAndReceiveAsType(routingKey, message, messagePostProcessor, null, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final String routingKey, final Object message,
            @Nullable final MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData,
            ParameterizedTypeReference<T> responseType) throws AmqpException {

        return convertSendAndReceiveAsType(this.exchange, routingKey, message, messagePostProcessor,
                correlationData, responseType);
    }

    @Override
    @Nullable
    public <T> T convertSendAndReceiveAsType(final String exchange, final String routingKey, final Object message,
            final MessagePostProcessor messagePostProcessor, ParameterizedTypeReference<T> responseType)
            throws AmqpException {

        return convertSendAndReceiveAsType(exchange, routingKey, message, messagePostProcessor, null, responseType);
    }

    @Override
    @SuppressWarnings(UNCHECKED)
    @Nullable
    public <T> T convertSendAndReceiveAsType(final String exchange, final String routingKey, final Object message,
            @Nullable final MessagePostProcessor messagePostProcessor,
            @Nullable final CorrelationData correlationData, ParameterizedTypeReference<T> responseType)
            throws AmqpException {

        Message replyMessage = convertSendAndReceiveRaw(exchange, routingKey, message, messagePostProcessor,
                correlationData);
        if (replyMessage == null) {
            return null;
        }
        return (T) getRequiredSmartMessageConverter().fromMessage(replyMessage, responseType);
    }

    /**
     * Convert and send a message and return the raw reply message, or null. Subclasses can
     * invoke this method if they want to perform conversion on the outbound message but
     * have direct access to the reply message before conversion.
     * @param exchange the exchange.
     * @param routingKey the routing key.
     * @param message the data to send.
     * @param messagePostProcessor a message post processor (can be null).
     * @param correlationData correlation data (can be null).
     * @return the reply message or null if a timeout occurs.
     * @since 1.6.6
     */
    @Nullable
    protected Message convertSendAndReceiveRaw(final String exchange, final String routingKey, final Object message,
            @Nullable final MessagePostProcessor messagePostProcessor,
            @Nullable final CorrelationData correlationData) {

        Message requestMessage = convertMessageIfNecessary(message);
        if (messagePostProcessor != null) {
            requestMessage = messagePostProcessor.postProcessMessage(requestMessage, correlationData);
        }
        return doSendAndReceive(exchange, routingKey, requestMessage, correlationData);
    }

    protected Message convertMessageIfNecessary(final Object object) {
        if (object instanceof Message) {
            return (Message) object;
        }
        return getRequiredMessageConverter().toMessage(object, new MessageProperties());
    }

    /**
     * Send a message and wait for a reply.
     *
     * @param exchange the exchange name
     * @param routingKey the routing key
     * @param message the message to send
     * @param correlationData the correlation data for confirms
     * @return the message that is received in reply
     */
    @Nullable
    protected Message doSendAndReceive(final String exchange, final String routingKey, final Message message,
            @Nullable CorrelationData correlationData) {

        if (!this.evaluatedFastReplyTo) {
            synchronized (this) {
                if (!this.evaluatedFastReplyTo) {
                    evaluateFastReplyTo();
                }
            }
        }

        if (this.usingFastReplyTo && this.useDirectReplyToContainer) {
            return doSendAndReceiveWithDirect(exchange, routingKey, message, correlationData);
        } else if (this.replyAddress == null || this.usingFastReplyTo) {
            return doSendAndReceiveWithTemporary(exchange, routingKey, message, correlationData);
        } else {
            return doSendAndReceiveWithFixed(exchange, routingKey, message, correlationData);
        }
    }

    @Nullable
    protected Message doSendAndReceiveWithTemporary(final String exchange, final String routingKey,
            final Message message, final CorrelationData correlationData) {

        return execute(channel -> {
            final PendingReply pendingReply = new PendingReply();
            String messageTag = String.valueOf(RabbitTemplate.this.messageTagProvider.incrementAndGet());
            RabbitTemplate.this.replyHolder.putIfAbsent(messageTag, pendingReply);

            Assert.isNull(message.getMessageProperties().getReplyTo(), "Send-and-receive methods can only be used "
                    + "if the Message does not already have a replyTo property.");
            String replyTo;
            if (RabbitTemplate.this.usingFastReplyTo) {
                replyTo = Address.AMQ_RABBITMQ_REPLY_TO;
            } else {
                DeclareOk queueDeclaration = channel.queueDeclare();
                replyTo = queueDeclaration.getQueue();
            }
            message.getMessageProperties().setReplyTo(replyTo);

            String consumerTag = UUID.randomUUID().toString();
            DefaultConsumer consumer = new TemplateConsumer(channel) {

                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                        byte[] body) {
                    MessageProperties messageProperties = RabbitTemplate.this.messagePropertiesConverter
                            .toMessageProperties(properties, envelope, RabbitTemplate.this.encoding);
                    Message reply = new Message(body, messageProperties);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Message received " + reply);
                    }
                    if (RabbitTemplate.this.afterReceivePostProcessors != null) {
                        for (MessagePostProcessor processor : RabbitTemplate.this.afterReceivePostProcessors) {
                            reply = processor.postProcessMessage(reply);
                        }
                    }
                    pendingReply.reply(reply);
                }

            };
            ClosingRecoveryListener.addRecoveryListenerIfNecessary(channel);
            ShutdownListener shutdownListener = c -> {
                if (!RabbitUtils.isNormalChannelClose(c)) {
                    pendingReply.completeExceptionally(c);
                }
            };
            channel.addShutdownListener(shutdownListener);
            channel.basicConsume(replyTo, true, consumerTag, this.noLocalReplyConsumer, true, null, consumer);
            Message reply = null;
            try {
                reply = exchangeMessages(exchange, routingKey, message, correlationData, channel, pendingReply,
                        messageTag);
            } finally {
                this.replyHolder.remove(messageTag);
                if (channel.isOpen()) {
                    cancelConsumerQuietly(channel, consumer);
                }
                try {
                    channel.removeShutdownListener(shutdownListener);
                } catch (Exception e) {
                    // NOSONAR - channel might have closed.
                }
            }
            return reply;
        }, obtainTargetConnectionFactory(this.sendConnectionFactorySelectorExpression, message));
    }

    private void cancelConsumerQuietly(Channel channel, DefaultConsumer consumer) {
        try {
            channel.basicCancel(consumer.getConsumerTag());
        } catch (Exception e) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Failed to cancel consumer: " + consumer, e);
            }
        }
    }

    @Nullable
    protected Message doSendAndReceiveWithFixed(final String exchange, final String routingKey,
            final Message message, final CorrelationData correlationData) {
        Assert.state(this.isListener, () -> "RabbitTemplate is not configured as MessageListener - "
                + "cannot use a 'replyAddress': " + this.replyAddress);
        return execute(channel -> {
            return doSendAndReceiveAsListener(exchange, routingKey, message, correlationData, channel);
        }, obtainTargetConnectionFactory(this.sendConnectionFactorySelectorExpression, message));
    }

    @Nullable
    private Message doSendAndReceiveWithDirect(String exchange, String routingKey, Message message,
            CorrelationData correlationData) {
        ConnectionFactory connectionFactory = obtainTargetConnectionFactory(
                this.sendConnectionFactorySelectorExpression, message);
        if (this.usePublisherConnection && connectionFactory.getPublisherConnectionFactory() != null) {
            connectionFactory = connectionFactory.getPublisherConnectionFactory();
        }
        DirectReplyToMessageListenerContainer container = this.directReplyToContainers.get(connectionFactory);
        if (container == null) {
            synchronized (this.directReplyToContainers) {
                container = this.directReplyToContainers.get(connectionFactory);
                if (container == null) {
                    container = new DirectReplyToMessageListenerContainer(connectionFactory);
                    container.setMessageListener(this);
                    container.setBeanName(this.beanName + "#" + this.containerInstance.getAndIncrement());
                    if (this.taskExecutor != null) {
                        container.setTaskExecutor(this.taskExecutor);
                    }
                    if (this.afterReceivePostProcessors != null) {
                        container.setAfterReceivePostProcessors(this.afterReceivePostProcessors
                                .toArray(new MessagePostProcessor[this.afterReceivePostProcessors.size()]));
                    }
                    container.setNoLocal(this.noLocalReplyConsumer);
                    if (this.replyErrorHandler != null) {
                        container.setErrorHandler(this.replyErrorHandler);
                    }
                    container.start();
                    this.directReplyToContainers.put(connectionFactory, container);
                    this.replyAddress = Address.AMQ_RABBITMQ_REPLY_TO;
                }
            }
        }
        ChannelHolder channelHolder = container.getChannelHolder();
        try {
            Channel channel = channelHolder.getChannel();
            if (this.confirmsOrReturnsCapable) {
                addListener(channel);
            }
            return doSendAndReceiveAsListener(exchange, routingKey, message, correlationData, channel);
        } catch (Exception e) {
            throw RabbitExceptionTranslator.convertRabbitAccessException(e);
        } finally {
            container.releaseConsumerFor(channelHolder, false, null);
        }
    }

    @Nullable
    private Message doSendAndReceiveAsListener(final String exchange, final String routingKey,
            final Message message, final CorrelationData correlationData, Channel channel) throws Exception { // NOSONAR
        final PendingReply pendingReply = new PendingReply();
        String messageTag = String.valueOf(this.messageTagProvider.incrementAndGet());
        if (this.userCorrelationId) {
            String correlationId;
            if (this.correlationKey != null) {
                correlationId = (String) message.getMessageProperties().getHeaders().get(this.correlationKey);
            } else {
                correlationId = message.getMessageProperties().getCorrelationId();
            }
            if (correlationId == null) {
                this.replyHolder.put(messageTag, pendingReply);
            } else {
                this.replyHolder.put(correlationId, pendingReply);
            }
        } else {
            this.replyHolder.put(messageTag, pendingReply);
        }
        saveAndSetProperties(message, pendingReply, messageTag);

        if (logger.isDebugEnabled()) {
            logger.debug("Sending message with tag " + messageTag);
        }
        Message reply = null;
        try {
            reply = exchangeMessages(exchange, routingKey, message, correlationData, channel, pendingReply,
                    messageTag);
        } finally {
            this.replyHolder.remove(messageTag);
        }
        return reply;
    }

    private void saveAndSetProperties(final Message message, final PendingReply pendingReply, String messageTag) {
        // Save any existing replyTo and correlation data
        String savedReplyTo = message.getMessageProperties().getReplyTo();
        pendingReply.setSavedReplyTo(savedReplyTo);
        if (StringUtils.hasLength(savedReplyTo) && logger.isDebugEnabled()) {
            logger.debug("Replacing replyTo header: " + savedReplyTo
                    + " in favor of template's configured reply-queue: " + RabbitTemplate.this.replyAddress);
        }
        message.getMessageProperties().setReplyTo(this.replyAddress);
        if (!this.userCorrelationId) {
            String savedCorrelation = null;
            if (this.correlationKey == null) { // using standard correlationId property
                String correlationId = message.getMessageProperties().getCorrelationId();
                if (correlationId != null) {
                    savedCorrelation = correlationId;
                }
            } else {
                savedCorrelation = (String) message.getMessageProperties().getHeaders().get(this.correlationKey);
            }
            pendingReply.setSavedCorrelation(savedCorrelation);
            if (this.correlationKey == null) { // using standard correlationId property
                message.getMessageProperties().setCorrelationId(messageTag);
            } else {
                message.getMessageProperties().setHeader(this.correlationKey, messageTag);
            }
        }
    }

    @Nullable
    private Message exchangeMessages(final String exchange, final String routingKey, final Message message,
            final CorrelationData correlationData, Channel channel, final PendingReply pendingReply,
            String messageTag) throws IOException, InterruptedException {

        Message reply;
        boolean mandatory = isMandatoryFor(message);
        if (mandatory && this.returnCallback == null) {
            message.getMessageProperties().getHeaders().put(RETURN_CORRELATION_KEY, messageTag);
        }
        doSend(channel, exchange, routingKey, message, mandatory, correlationData);
        reply = this.replyTimeout < 0 ? pendingReply.get()
                : pendingReply.get(this.replyTimeout, TimeUnit.MILLISECONDS);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Reply: " + reply);
        }
        if (reply == null) {
            replyTimedOut(message.getMessageProperties().getCorrelationId());
        }
        return reply;
    }

    /**
     * Subclasses can implement this to be notified that a reply has timed out.
     * @param correlationId the correlationId
     * @since 2.1.2
     */
    protected void replyTimedOut(String correlationId) {
        // NOSONAR
    }

    /**
     * Return whether the provided message should be sent with the mandatory flag set.
     * @param message the message.
     * @return true for mandatory.
     * @since 2.0
     */
    public Boolean isMandatoryFor(final Message message) {
        Boolean value = this.mandatoryExpression.getValue(this.evaluationContext, message, Boolean.class);
        return value != null ? value : Boolean.FALSE;
    }

    @Override
    @Nullable
    public <T> T execute(ChannelCallback<T> action) {
        return execute(action, getConnectionFactory());
    }

    @SuppressWarnings(UNCHECKED)
    @Nullable
    private <T> T execute(final ChannelCallback<T> action, final ConnectionFactory connectionFactory) {
        if (this.retryTemplate != null) {
            try {
                return this.retryTemplate.execute(
                        (RetryCallback<T, Exception>) context -> doExecute(action, connectionFactory),
                        (RecoveryCallback<T>) this.recoveryCallback);
            } catch (RuntimeException e) { // NOSONAR catch and rethrow needed to avoid next catch
                throw e;
            } catch (Exception e) {
                throw RabbitExceptionTranslator.convertRabbitAccessException(e);
            }
        } else {
            return doExecute(action, connectionFactory);
        }
    }

    @Nullable
    private <T> T doExecute(ChannelCallback<T> action, ConnectionFactory connectionFactory) { // NOSONAR complexity
        Assert.notNull(action, "Callback object must not be null");
        Channel channel = null;
        boolean invokeScope = false;
        // No need to check the thread local if we know that no invokes are in process
        if (this.activeTemplateCallbacks.get() > 0) {
            channel = this.dedicatedChannels.get();
        }
        RabbitResourceHolder resourceHolder = null;
        Connection connection = null; // NOSONAR (close)
        if (channel == null) {
            if (isChannelTransacted()) {
                resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(connectionFactory, true,
                        this.usePublisherConnection);
                channel = resourceHolder.getChannel();
                if (channel == null) {
                    ConnectionFactoryUtils.releaseResources(resourceHolder);
                    throw new IllegalStateException("Resource holder returned a null channel");
                }
            } else {
                connection = ConnectionFactoryUtils.createConnection(connectionFactory,
                        this.usePublisherConnection); // NOSONAR - RabbitUtils closes
                if (connection == null) {
                    throw new IllegalStateException("Connection factory returned a null connection");
                }
                try {
                    channel = connection.createChannel(false);
                    if (channel == null) {
                        throw new IllegalStateException("Connection returned a null channel");
                    }
                } catch (RuntimeException e) {
                    RabbitUtils.closeConnection(connection);
                    throw e;
                }
            }
        } else {
            invokeScope = true;
        }
        try {
            return invokeAction(action, connectionFactory, channel);
        } catch (Exception ex) {
            if (isChannelLocallyTransacted(channel)) {
                resourceHolder.rollbackAll();
            }
            throw convertRabbitAccessException(ex);
        } finally {
            cleanUpAfterAction(channel, invokeScope, resourceHolder, connection);
        }
    }

    private void cleanUpAfterAction(Channel channel, boolean invokeScope, RabbitResourceHolder resourceHolder,
            Connection connection) {

        if (!invokeScope) {
            if (resourceHolder != null) {
                ConnectionFactoryUtils.releaseResources(resourceHolder);
            } else {
                RabbitUtils.closeChannel(channel);
                RabbitUtils.closeConnection(connection);
            }
        }
    }

    @Nullable
    private <T> T invokeAction(ChannelCallback<T> action, ConnectionFactory connectionFactory, Channel channel)
            throws Exception { // NOSONAR see the callback

        if (this.confirmsOrReturnsCapable == null) {
            determineConfirmsReturnsCapability(connectionFactory);
        }
        if (this.confirmsOrReturnsCapable) {
            addListener(channel);
        }
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Executing callback " + action.getClass().getSimpleName() + " on RabbitMQ Channel: " + channel);
        }
        return action.doInRabbit(channel);
    }

    @Override
    @Nullable
    public <T> T invoke(OperationsCallback<T> action, @Nullable com.rabbitmq.client.ConfirmCallback acks,
            @Nullable com.rabbitmq.client.ConfirmCallback nacks) {

        final Channel currentChannel = this.dedicatedChannels.get();
        Assert.state(currentChannel == null, () -> "Nested invoke() calls are not supported; channel '"
                + currentChannel + "' is already associated with this thread");
        this.activeTemplateCallbacks.incrementAndGet();
        RabbitResourceHolder resourceHolder = null;
        Connection connection = null; // NOSONAR (close)
        Channel channel;
        if (isChannelTransacted()) {
            resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(getConnectionFactory(), true);
            channel = resourceHolder.getChannel();
            if (channel == null) {
                ConnectionFactoryUtils.releaseResources(resourceHolder);
                throw new IllegalStateException("Resource holder returned a null channel");
            }
        } else {
            connection = getConnectionFactory().createConnection(); // NOSONAR - RabbitUtils
            if (connection == null) {
                throw new IllegalStateException("Connection factory returned a null connection");
            }
            try {
                channel = connection.createChannel(false);
                if (channel == null) {
                    throw new IllegalStateException("Connection returned a null channel");
                }
                if (!getConnectionFactory().isPublisherConfirms()) {
                    RabbitUtils.setPhysicalCloseRequired(channel, true);
                }
                this.dedicatedChannels.set(channel);
            } catch (RuntimeException e) {
                RabbitUtils.closeConnection(connection);
                throw e;
            }
        }
        ConfirmListener listener = addConfirmListener(acks, nacks, channel);
        try {
            return action.doInRabbit(this);
        } finally {
            cleanUpAfterAction(resourceHolder, connection, channel, listener);
        }
    }

    @Nullable
    private ConfirmListener addConfirmListener(@Nullable com.rabbitmq.client.ConfirmCallback acks,
            @Nullable com.rabbitmq.client.ConfirmCallback nacks, Channel channel) {
        ConfirmListener listener = null;
        if (acks != null && nacks != null && channel instanceof ChannelProxy
                && ((ChannelProxy) channel).isConfirmSelected()) {
            listener = channel.addConfirmListener(acks, nacks);
        }
        return listener;
    }

    private void cleanUpAfterAction(RabbitResourceHolder resourceHolder, Connection connection, Channel channel,
            ConfirmListener listener) {

        if (listener != null) {
            channel.removeConfirmListener(listener);
        }
        this.activeTemplateCallbacks.decrementAndGet();
        this.dedicatedChannels.remove();
        if (resourceHolder != null) {
            ConnectionFactoryUtils.releaseResources(resourceHolder);
        } else {
            RabbitUtils.closeChannel(channel);
            RabbitUtils.closeConnection(connection);
        }
    }

    @Override
    public boolean waitForConfirms(long timeout) {
        Channel channel = this.dedicatedChannels.get();
        Assert.state(channel != null, "This operation is only available within the scope of an invoke operation");
        try {
            return channel.waitForConfirms(timeout);
        } catch (TimeoutException e) {
            throw RabbitExceptionTranslator.convertRabbitAccessException(e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw RabbitExceptionTranslator.convertRabbitAccessException(e);
        }
    }

    @Override
    public void waitForConfirmsOrDie(long timeout) {
        Channel channel = this.dedicatedChannels.get();
        Assert.state(channel != null, "This operation is only available within the scope of an invoke operation");
        try {
            channel.waitForConfirmsOrDie(timeout);
        } catch (IOException | TimeoutException e) {
            throw RabbitExceptionTranslator.convertRabbitAccessException(e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw RabbitExceptionTranslator.convertRabbitAccessException(e);
        }
    }

    public void determineConfirmsReturnsCapability(ConnectionFactory connectionFactory) {
        this.publisherConfirms = connectionFactory.isPublisherConfirms();
        this.confirmsOrReturnsCapable = this.publisherConfirms || connectionFactory.isPublisherReturns();
    }

    /**
     * Send the given message to the specified exchange.
     *
     * @param channel The RabbitMQ Channel to operate within.
     * @param exchangeArg The name of the RabbitMQ exchange to send to.
     * @param routingKeyArg The routing key.
     * @param message The Message to send.
     * @param mandatory The mandatory flag.
     * @param correlationData The correlation data.
     * @throws IOException If thrown by RabbitMQ API methods.
     */
    public void doSend(Channel channel, String exchangeArg, String routingKeyArg, Message message, // NOSONAR complexity
            boolean mandatory, @Nullable CorrelationData correlationData) throws IOException {

        String exch = exchangeArg;
        String rKey = routingKeyArg;
        if (exch == null) {
            exch = this.exchange;
        }
        if (rKey == null) {
            rKey = this.routingKey;
        }
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Publishing message " + message + "on exchange [" + exch + "], routingKey = [" + rKey + "]");
        }

        Message messageToUse = message;
        MessageProperties messageProperties = messageToUse.getMessageProperties();
        if (mandatory) {
            messageProperties.getHeaders().put(PublisherCallbackChannel.RETURN_LISTENER_CORRELATION_KEY, this.uuid);
        }
        if (this.beforePublishPostProcessors != null) {
            for (MessagePostProcessor processor : this.beforePublishPostProcessors) {
                messageToUse = processor.postProcessMessage(messageToUse, correlationData);
            }
        }
        setupConfirm(channel, messageToUse, correlationData);
        if (this.userIdExpression != null && messageProperties.getUserId() == null) {
            String userId = this.userIdExpression.getValue(this.evaluationContext, messageToUse, String.class);
            if (userId != null) {
                messageProperties.setUserId(userId);
            }
        }
        sendToRabbit(channel, exch, rKey, mandatory, messageToUse);
        // Check if commit needed
        if (isChannelLocallyTransacted(channel)) {
            // Transacted channel created by this template -> commit.
            RabbitUtils.commitIfNecessary(channel);
        }
    }

    protected void sendToRabbit(Channel channel, String exchange, String routingKey, boolean mandatory,
            Message message) throws IOException {
        BasicProperties convertedMessageProperties = this.messagePropertiesConverter
                .fromMessageProperties(message.getMessageProperties(), this.encoding);
        channel.basicPublish(exchange, routingKey, mandatory, convertedMessageProperties, message.getBody());
    }

    private void setupConfirm(Channel channel, Message message, @Nullable CorrelationData correlationDataArg) {
        if ((this.publisherConfirms || this.confirmCallback != null)
                && channel instanceof PublisherCallbackChannel) {

            PublisherCallbackChannel publisherCallbackChannel = (PublisherCallbackChannel) channel;
            CorrelationData correlationData = this.correlationDataPostProcessor != null
                    ? this.correlationDataPostProcessor.postProcess(message, correlationDataArg)
                    : correlationDataArg;
            long nextPublishSeqNo = channel.getNextPublishSeqNo();
            message.getMessageProperties().setPublishSequenceNumber(nextPublishSeqNo);
            publisherCallbackChannel.addPendingConfirm(this, nextPublishSeqNo,
                    new PendingConfirm(correlationData, System.currentTimeMillis()));
            if (correlationData != null && StringUtils.hasText(correlationData.getId())) {
                message.getMessageProperties().setHeader(PublisherCallbackChannel.RETURNED_MESSAGE_CORRELATION_KEY,
                        correlationData.getId());
            }
        } else if (channel instanceof ChannelProxy && ((ChannelProxy) channel).isConfirmSelected()) {
            long nextPublishSeqNo = channel.getNextPublishSeqNo();
            message.getMessageProperties().setPublishSequenceNumber(nextPublishSeqNo);
        }
    }

    /**
     * Check whether the given Channel is locally transacted, that is, whether its transaction is managed by this
     * template's Channel handling and not by an external transaction coordinator.
     * @param channel the Channel to check
     * @return whether the given Channel is locally transacted
     * @see ConnectionFactoryUtils#isChannelTransactional
     * @see #isChannelTransacted
     */
    protected boolean isChannelLocallyTransacted(Channel channel) {
        return isChannelTransacted()
                && !ConnectionFactoryUtils.isChannelTransactional(channel, getConnectionFactory());
    }

    private Message buildMessageFromDelivery(Delivery delivery) {
        return buildMessage(delivery.getEnvelope(), delivery.getProperties(), delivery.getBody(), -1);
    }

    private Message buildMessageFromResponse(GetResponse response) {
        return buildMessage(response.getEnvelope(), response.getProps(), response.getBody(),
                response.getMessageCount());
    }

    private Message buildMessage(Envelope envelope, BasicProperties properties, byte[] body, int msgCount) {
        MessageProperties messageProps = this.messagePropertiesConverter.toMessageProperties(properties, envelope,
                this.encoding);
        if (msgCount >= 0) {
            messageProps.setMessageCount(msgCount);
        }
        Message message = new Message(body, messageProps);
        if (this.afterReceivePostProcessors != null) {
            for (MessagePostProcessor processor : this.afterReceivePostProcessors) {
                message = processor.postProcessMessage(message);
            }
        }
        return message;
    }

    private MessageConverter getRequiredMessageConverter() throws IllegalStateException {
        MessageConverter converter = getMessageConverter();
        if (converter == null) {
            throw new AmqpIllegalStateException(
                    "No 'messageConverter' specified. Check configuration of RabbitTemplate.");
        }
        return converter;
    }

    private SmartMessageConverter getRequiredSmartMessageConverter() throws IllegalStateException {
        MessageConverter converter = getRequiredMessageConverter();
        Assert.state(converter instanceof SmartMessageConverter,
                "template's message converter must be a SmartMessageConverter");
        return (SmartMessageConverter) converter;
    }

    private String getRequiredQueue() throws IllegalStateException {
        String name = this.defaultReceiveQueue;
        if (name == null) {
            throw new AmqpIllegalStateException("No 'queue' specified. Check configuration of RabbitTemplate.");
        }
        return name;
    }

    /**
     * Determine a reply-to Address for the given message.
     * <p>
     * The default implementation first checks the Rabbit Reply-To Address of the supplied request; if that is not
     * <code>null</code> it is returned; if it is <code>null</code>, then the configured default Exchange and
     * routing key are used to construct a reply-to Address. If the exchange property is also <code>null</code>,
     * then an {@link AmqpException} is thrown.
     * @param request the original incoming Rabbit message
     * @return the reply-to Address (never <code>null</code>)
     * @throws AmqpException if no {@link Address} can be determined
     * @see org.springframework.amqp.core.Message#getMessageProperties()
     * @see org.springframework.amqp.core.MessageProperties#getReplyTo()
     */
    private Address getReplyToAddress(Message request) throws AmqpException {
        Address replyTo = request.getMessageProperties().getReplyToAddress();
        if (replyTo == null) {
            if (this.exchange == null) {
                throw new AmqpException("Cannot determine ReplyTo message property value: "
                        + "Request message does not contain reply-to property, "
                        + "and no default Exchange was set.");
            }
            replyTo = new Address(this.exchange, this.routingKey);
        }
        return replyTo;
    }

    /**
     * Add this template as a confirms listener for the provided channel.
     * @param channel the channel.
     * @since 2.0
     */
    public void addListener(Channel channel) {
        if (channel instanceof PublisherCallbackChannel) {
            PublisherCallbackChannel publisherCallbackChannel = (PublisherCallbackChannel) channel;
            Channel key = channel instanceof ChannelProxy ? ((ChannelProxy) channel).getTargetChannel() : channel;
            if (this.publisherConfirmChannels.putIfAbsent(key, this) == null) {
                publisherCallbackChannel.addListener(this);
                if (logger.isDebugEnabled()) {
                    logger.debug("Added publisher confirm channel: " + channel + " to map, size now "
                            + this.publisherConfirmChannels.size());
                }
            }
        } else {
            throw new IllegalStateException("Channel does not support confirms or returns; "
                    + "is the connection factory configured for confirms or returns?");
        }
    }

    @Override
    public void handleConfirm(PendingConfirm pendingConfirm, boolean ack) {
        if (this.confirmCallback != null) {
            this.confirmCallback.confirm(pendingConfirm.getCorrelationData(), ack, pendingConfirm.getCause()); // NOSONAR never null
        }
    }

    @Override
    public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
            BasicProperties properties, byte[] body) {

        ReturnCallback callback = this.returnCallback;
        if (callback == null) {
            Object messageTagHeader = properties.getHeaders().remove(RETURN_CORRELATION_KEY);
            if (messageTagHeader != null) {
                String messageTag = messageTagHeader.toString();
                final PendingReply pendingReply = this.replyHolder.get(messageTag);
                if (pendingReply != null) {
                    callback = (message, replyCode1, replyText1, exchange1, routingKey1) -> pendingReply
                            .returned(new AmqpMessageReturnedException("Message returned", message, replyCode1,
                                    replyText1, exchange1, routingKey1));
                } else if (logger.isWarnEnabled()) {
                    logger.warn("Returned request message but caller has timed out");
                }
            } else if (logger.isWarnEnabled()) {
                logger.warn("Returned message but no callback available");
            }
        }
        if (callback != null) {
            properties.getHeaders().remove(PublisherCallbackChannel.RETURN_LISTENER_CORRELATION_KEY);
            MessageProperties messageProperties = this.messagePropertiesConverter.toMessageProperties(properties,
                    null, this.encoding);
            Message returnedMessage = new Message(body, messageProperties);
            callback.returnedMessage(returnedMessage, replyCode, replyText, exchange, routingKey);
        }
    }

    @Override
    public boolean isConfirmListener() {
        return this.confirmCallback != null;
    }

    @Override
    public boolean isReturnListener() {
        return true;
    }

    @Override
    public void revoke(Channel channel) {
        this.publisherConfirmChannels.remove(channel);
        if (logger.isDebugEnabled()) {
            logger.debug("Removed publisher confirm channel: " + channel + " from map, size now "
                    + this.publisherConfirmChannels.size());
        }
    }

    @Override
    public String getUUID() {
        return this.uuid;
    }

    @Override
    public void onMessage(Message message) {
        if (logger.isTraceEnabled()) {
            logger.trace("Message received " + message);
        }
        String messageTag;
        if (this.correlationKey == null) { // using standard correlationId property
            messageTag = message.getMessageProperties().getCorrelationId();
        } else {
            messageTag = (String) message.getMessageProperties().getHeaders().get(this.correlationKey);
        }
        if (messageTag == null) {
            throw new AmqpRejectAndDontRequeueException("No correlation header in reply");
        }

        PendingReply pendingReply = this.replyHolder.get(messageTag);
        if (pendingReply == null) {
            if (logger.isWarnEnabled()) {
                logger.warn("Reply received after timeout for " + messageTag);
            }
            throw new AmqpRejectAndDontRequeueException("Reply received after timeout");
        } else {
            restoreProperties(message, pendingReply);
            pendingReply.reply(message);
        }
    }

    private void restoreProperties(Message message, PendingReply pendingReply) {
        if (!this.userCorrelationId) {
            // Restore the inbound correlation data
            String savedCorrelation = pendingReply.getSavedCorrelation();
            if (this.correlationKey == null) {
                message.getMessageProperties().setCorrelationId(savedCorrelation);
            } else {
                if (savedCorrelation != null) {
                    message.getMessageProperties().setHeader(this.correlationKey, savedCorrelation);
                } else {
                    message.getMessageProperties().getHeaders().remove(this.correlationKey);
                }
            }
        }
        // Restore any inbound replyTo
        String savedReplyTo = pendingReply.getSavedReplyTo();
        message.getMessageProperties().setReplyTo(savedReplyTo);
        if (logger.isDebugEnabled() && savedReplyTo != null) {
            logger.debug("Restored replyTo to " + savedReplyTo);
        }
    }

    private DefaultConsumer createConsumer(final String queueName, Channel channel,
            CompletableFuture<Delivery> future, long timeoutMillis)
            throws IOException, TimeoutException, InterruptedException {

        channel.basicQos(1);
        final CountDownLatch latch = new CountDownLatch(1);
        DefaultConsumer consumer = new TemplateConsumer(channel) {

            @Override
            public void handleCancel(String consumerTag) {
                future.completeExceptionally(new ConsumerCancelledException());
            }

            @Override
            public void handleConsumeOk(String consumerTag) {
                super.handleConsumeOk(consumerTag);
                latch.countDown();
            }

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) {
                future.complete(new Delivery(consumerTag, envelope, properties, body, queueName));
            }

        };
        channel.basicConsume(queueName, consumer);
        if (!latch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
            if (channel instanceof ChannelProxy) {
                ((ChannelProxy) channel).getTargetChannel().close();
            }
            future.completeExceptionally(new ConsumeOkNotReceivedException(
                    "Blocking receive, consumer failed to consume within " + timeoutMillis + " ms: " + consumer));
        }
        return consumer;
    }

    private static class PendingReply {

        @Nullable
        private volatile String savedReplyTo;

        @Nullable
        private volatile String savedCorrelation;

        private final CompletableFuture<Message> future = new CompletableFuture<>();

        @Nullable
        public String getSavedReplyTo() {
            return this.savedReplyTo;
        }

        public void setSavedReplyTo(@Nullable String savedReplyTo) {
            this.savedReplyTo = savedReplyTo;
        }

        @Nullable
        public String getSavedCorrelation() {
            return this.savedCorrelation;
        }

        public void setSavedCorrelation(@Nullable String savedCorrelation) {
            this.savedCorrelation = savedCorrelation;
        }

        public Message get() throws InterruptedException {
            try {
                return this.future.get();
            } catch (ExecutionException e) {
                throw RabbitExceptionTranslator.convertRabbitAccessException(e.getCause()); // NOSONAR lost stack trace
            }
        }

        @Nullable
        public Message get(long timeout, TimeUnit unit) throws InterruptedException {
            try {
                return this.future.get(timeout, unit);
            } catch (ExecutionException e) {
                throw RabbitExceptionTranslator.convertRabbitAccessException(e.getCause()); // NOSONAR lost stack trace
            } catch (TimeoutException e) {
                return null;
            }
        }

        public void reply(Message reply) {
            this.future.complete(reply);
        }

        public void returned(AmqpMessageReturnedException e) {
            completeExceptionally(e);
        }

        public void completeExceptionally(Throwable t) {
            this.future.completeExceptionally(t);
        }

    }

    /**
     * Adds {@link #toString()} to the {@link DefaultConsumer}.
     * @since 2.0
     */
    protected abstract static class TemplateConsumer extends DefaultConsumer {

        public TemplateConsumer(Channel channel) {
            super(channel);
        }

        @Override
        public String toString() {
            return "TemplateConsumer [channel=" + this.getChannel() + ", consumerTag=" + this.getConsumerTag()
                    + "]";
        }

    }

    /**
     * A callback for publisher confirmations.
     *
     */
    @FunctionalInterface
    public interface ConfirmCallback {

        /**
         * Confirmation callback.
         * @param correlationData correlation data for the callback.
         * @param ack true for ack, false for nack
         * @param cause An optional cause, for nack, when available, otherwise null.
         */
        void confirm(@Nullable CorrelationData correlationData, boolean ack, @Nullable String cause);

    }

    /**
     * A callback for returned messages.
     *
     */
    @FunctionalInterface
    public interface ReturnCallback {

        /**
         * Returned message callback.
         * @param message the returned message.
         * @param replyCode the reply code.
         * @param replyText the reply text.
         * @param exchange the exchange.
         * @param routingKey the routing key.
         */
        void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey);

    }

}