org.springframework.amqp.rabbit.AsyncRabbitTemplate.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright 2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.amqp.rabbit;

import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledFuture;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.AmqpIllegalStateException;
import org.springframework.amqp.core.Address;
import org.springframework.amqp.core.AmqpMessageReturnedException;
import org.springframework.amqp.core.AmqpReplyTimeoutException;
import org.springframework.amqp.core.AsyncAmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
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.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.rabbit.support.PublisherCallbackChannel;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.expression.Expression;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;

import com.rabbitmq.client.Channel;

/**
 * Provides asynchronous send and receive operations returning a {@link ListenableFuture}
 * allowing the caller to obtain the reply later, using {@code get()} or a callback.
 * <p>
 * When confirms are enabled, the future has a confirm property which is itself a
 * {@link ListenableFuture}. If the reply is received before the publisher confirm,
 * the confirm is discarded since the reply implicitly indicates the message was
 * published.
 * <p>
 * Returned (undeliverable) request messages are presented as a
 * {@link AmqpMessageReturnedException} cause of an
 * {@link java.util.concurrent.ExecutionException}.
 * <p>
 * Internally, the template uses a {@link RabbitTemplate} and an
 * {@link AbstractMessageListenerContainer} either provided or constructed internally
 * (a {@link SimpleMessageListenerContainer}).
 * If an external {@link RabbitTemplate} is provided and confirms/returns are enabled,
 * it must not previously have had callbacks registered because this object needs to
 * be the callback.
 *
 * @author Gary Russell
 * @since 1.6
 */
public class AsyncRabbitTemplate implements AsyncAmqpTemplate, ChannelAwareMessageListener, ReturnCallback,
        ConfirmCallback, BeanNameAware, SmartLifecycle {

    public static final int DEFAULT_RECEIVE_TIMEOUT = 30000;

    private final Log logger = LogFactory.getLog(this.getClass());

    private final RabbitTemplate template;

    private final AbstractMessageListenerContainer container;

    private final DirectReplyToMessageListenerContainer directReplyToContainer;

    private final String replyAddress;

    @SuppressWarnings("rawtypes")
    private final ConcurrentMap<String, RabbitFuture> pending = new ConcurrentHashMap<String, RabbitFuture>();

    private volatile boolean running;

    private volatile boolean enableConfirms;

    private volatile long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;

    private int phase;

    private boolean autoStartup = true;

    private String beanName;

    private TaskScheduler taskScheduler;

    /**
     * Construct an instance using the provided arguments. Replies will be
     * routed to the default exchange using the reply queue name as the routing
     * key.
     * @param connectionFactory the connection factory.
     * @param exchange the default exchange to which requests will be sent.
     * @param routingKey the default routing key.
     * @param replyQueue the name of the reply queue to listen for replies.
     */
    public AsyncRabbitTemplate(ConnectionFactory connectionFactory, String exchange, String routingKey,
            String replyQueue) {
        this(connectionFactory, exchange, routingKey, replyQueue, null);
    }

    /**
     * Construct an instance using the provided arguments. If 'replyAddress' is null,
     * replies will be routed to the default exchange using the reply queue name as the
     * routing key. Otherwise it should have the form exchange/routingKey and must
     * cause messages to be routed to the reply queue.
     * @param connectionFactory the connection factory.
     * @param exchange the default exchange to which requests will be sent.
     * @param routingKey the default routing key.
     * @param replyQueue the name of the reply queue to listen for replies.
     * @param replyAddress the reply address (exchange/routingKey).
     */
    public AsyncRabbitTemplate(ConnectionFactory connectionFactory, String exchange, String routingKey,
            String replyQueue, String replyAddress) {
        Assert.notNull(connectionFactory, "'connectionFactory' cannot be null");
        Assert.notNull(routingKey, "'routingKey' cannot be null");
        Assert.notNull(replyQueue, "'replyQueue' cannot be null");
        this.template = new RabbitTemplate(connectionFactory);
        this.template.setExchange(exchange == null ? "" : exchange);
        this.template.setRoutingKey(routingKey);
        this.container = new SimpleMessageListenerContainer(connectionFactory);
        this.container.setQueueNames(replyQueue);
        this.container.setMessageListener(this);
        this.container.afterPropertiesSet();
        this.directReplyToContainer = null;
        if (replyAddress == null) {
            this.replyAddress = replyQueue;
        } else {
            this.replyAddress = replyAddress;
        }

    }

    /**
     * Construct an instance using the provided arguments. The first queue the container
     * is configured to listen to will be used as the reply queue. Replies will be
     * routed using the default exchange with that queue name as the routing key.
     * @param template a {@link RabbitTemplate}
     * @param container a {@link AbstractMessageListenerContainer}.
     */
    public AsyncRabbitTemplate(RabbitTemplate template, AbstractMessageListenerContainer container) {
        this(template, container, null);
    }

    /**
     * Construct an instance using the provided arguments. The first queue the container
     * is configured to listen to will be used as the reply queue. If 'replyAddress' is
     * null, replies will be routed using the default exchange with that queue name as the
     * routing key. Otherwise it should have the form exchange/routingKey and must
     * cause messages to be routed to the reply queue.
     * @param template a {@link RabbitTemplate}.
     * @param container a {@link AbstractMessageListenerContainer}.
     * @param replyAddress the reply address.
     */
    public AsyncRabbitTemplate(RabbitTemplate template, AbstractMessageListenerContainer container,
            String replyAddress) {
        Assert.notNull(template, "'template' cannot be null");
        Assert.notNull(container, "'container' cannot be null");
        this.template = template;
        this.container = container;
        this.container.setMessageListener(this);
        this.directReplyToContainer = null;
        if (replyAddress == null) {
            this.replyAddress = container.getQueueNames()[0];
        } else {
            this.replyAddress = replyAddress;
        }
    }

    /**
     * Construct an instance using the provided arguments. "Direct replyTo" is used for
     * replies.
     * @param connectionFactory the connection factory.
     * @param exchange the default exchange to which requests will be sent.
     * @param routingKey the default routing key.
     * @since 2.0
     */
    public AsyncRabbitTemplate(ConnectionFactory connectionFactory, String exchange, String routingKey) {
        Assert.notNull(connectionFactory, "'connectionFactory' cannot be null");
        Assert.notNull(routingKey, "'routingKey' cannot be null");
        this.template = new RabbitTemplate(connectionFactory);
        this.template.setExchange(exchange == null ? "" : exchange);
        this.template.setRoutingKey(routingKey);
        this.container = null;
        this.replyAddress = null;
        this.directReplyToContainer = new DirectReplyToMessageListenerContainer(
                this.template.getConnectionFactory());
        this.directReplyToContainer.setChannelAwareMessageListener(this);
    }

    /**
     * Construct an instance using the provided arguments. "Direct replyTo" is used for
     * replies.
     * @param template a {@link RabbitTemplate}
     * @since 2.0
     */
    public AsyncRabbitTemplate(RabbitTemplate template) {
        Assert.notNull(template, "'template' cannot be null");
        this.template = template;
        this.container = null;
        this.replyAddress = null;
        this.directReplyToContainer = new DirectReplyToMessageListenerContainer(
                this.template.getConnectionFactory());
        this.directReplyToContainer.setChannelAwareMessageListener(this);
    }

    /**
     * @param autoStartup true for auto start.
     * @see #isAutoStartup()
     */
    public void setAutoStartup(boolean autoStartup) {
        this.autoStartup = autoStartup;
    }

    /**
     * @param phase the phase.
     * @see #getPhase()
     */
    public void setPhase(int phase) {
        this.phase = phase;
    }

    /**
     * Set to true to enable the receipt of returned messages that cannot be delivered
     * in the form of a {@link AmqpMessageReturnedException}.
     * @param mandatory true to enable returns.
     */
    public void setMandatory(boolean mandatory) {
        this.template.setReturnCallback(this);
        this.template.setMandatory(mandatory);
    }

    /**
     * @param mandatoryExpression a SpEL {@link Expression} to evaluate against each request
     * message. The result of the evaluation must be a {@code boolean} value.
     * @since 2.0
     */
    public void setMandatoryExpression(Expression mandatoryExpression) {
        this.template.setReturnCallback(this);
        this.template.setMandatoryExpression(mandatoryExpression);
    }

    /**
     * @param mandatoryExpression a SpEL {@link Expression} to evaluate against each request
     * message. The result of the evaluation must be a {@code boolean} value.
     * @since 2.0
     */
    public void setMandatoryExpressionString(String mandatoryExpression) {
        this.template.setReturnCallback(this);
        this.template.setMandatoryExpressionString(mandatoryExpression);
    }

    /**
     * Set to true to enable publisher confirms. When enabled, the {@link RabbitFuture}
     * returned by the send and receive operation will have a
     * {@code ListenableFuture<Boolean>} in its {@code confirm} property.
     * @param enableConfirms true to enable publisher confirms.
     */
    public void setEnableConfirms(boolean enableConfirms) {
        this.enableConfirms = enableConfirms;
        if (enableConfirms) {
            this.template.setConfirmCallback(this);
        }
    }

    public String getBeanName() {
        return this.beanName;
    }

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

    /**
     * @return a reference to the underlying connection factory in the
     * {@link RabbitTemplate}.
     */
    public ConnectionFactory getConnectionFactory() {
        return this.template.getConnectionFactory();
    }

    /**
     * Set the receive timeout - the future returned by the send and receive
     * methods will be canceled when this timeout expires. {@code <= 0} means
     * futures never expire. Beware that this will cause a memory leak if a
     * reply is not received. Default: 30000 (30 seconds).
     * @param receiveTimeout the timeout in milliseconds.
     */
    public void setReceiveTimeout(long receiveTimeout) {
        this.receiveTimeout = receiveTimeout;
    }

    /**
     * Set the task scheduler to expire timed out futures.
     * @param taskScheduler the task scheduler
     * @see #setReceiveTimeout(long)
     */
    public void setTaskScheduler(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler; // NOSONAR synchronization
    }

    /**
     * @return a reference to the underlying {@link RabbitTemplate}'s
     * {@link MessageConverter}.
     */
    public MessageConverter getMessageConverter() {
        return this.template.getMessageConverter();
    }

    @Override
    public RabbitMessageFuture sendAndReceive(Message message) {
        return sendAndReceive(this.template.getExchange(), this.template.getRoutingKey(), message);
    }

    @Override
    public RabbitMessageFuture sendAndReceive(String routingKey, Message message) {
        return sendAndReceive(this.template.getExchange(), routingKey, message);
    }

    @Override
    public RabbitMessageFuture sendAndReceive(String exchange, String routingKey, Message message) {
        String correlationId = getOrSetCorrelationIdAndSetReplyTo(message);
        RabbitMessageFuture future = new RabbitMessageFuture(correlationId, message);
        CorrelationData correlationData = null;
        if (this.enableConfirms) {
            correlationData = new CorrelationData(correlationId);
            future.setConfirm(new SettableListenableFuture<Boolean>());
        }
        this.pending.put(correlationId, future);
        if (this.container != null) {
            this.template.send(exchange, routingKey, message, correlationData);
        } else {
            ChannelHolder channelHolder = this.directReplyToContainer.getChannelHolder();
            future.setChannelHolder(channelHolder);
            sendDirect(channelHolder.getChannel(), exchange, routingKey, message, correlationData);
        }
        future.startTimer();
        return future;
    }

    @Override
    public <C> RabbitConverterFuture<C> convertSendAndReceive(Object object) {
        return convertSendAndReceive(this.template.getExchange(), this.template.getRoutingKey(), object, null);
    }

    @Override
    public <C> RabbitConverterFuture<C> convertSendAndReceive(String routingKey, Object object) {
        return convertSendAndReceive(this.template.getExchange(), routingKey, object, null);
    }

    @Override
    public <C> RabbitConverterFuture<C> convertSendAndReceive(String exchange, String routingKey, Object object) {
        return convertSendAndReceive(exchange, routingKey, object, null);
    }

    @Override
    public <C> RabbitConverterFuture<C> convertSendAndReceive(Object object,
            MessagePostProcessor messagePostProcessor) {
        return convertSendAndReceive(this.template.getExchange(), this.template.getRoutingKey(), object,
                messagePostProcessor);
    }

    @Override
    public <C> RabbitConverterFuture<C> convertSendAndReceive(String routingKey, Object object,
            MessagePostProcessor messagePostProcessor) {
        return convertSendAndReceive(this.template.getExchange(), routingKey, object, messagePostProcessor);
    }

    @Override
    public <C> RabbitConverterFuture<C> convertSendAndReceive(String exchange, String routingKey, Object object,
            MessagePostProcessor messagePostProcessor) {
        CorrelationData correlationData = null;
        if (this.enableConfirms) {
            correlationData = new CorrelationData(null);
        }
        CorrelationMessagePostProcessor<C> correlationPostProcessor = new CorrelationMessagePostProcessor<C>(
                messagePostProcessor, correlationData);
        if (this.container != null) {
            this.template.convertAndSend(exchange, routingKey, object, correlationPostProcessor, correlationData);
        } else {
            MessageConverter converter = this.template.getMessageConverter();
            if (converter == null) {
                throw new AmqpIllegalStateException(
                        "No 'messageConverter' specified. Check configuration of RabbitTemplate.");
            }
            Message message = converter.toMessage(object, new MessageProperties());
            correlationPostProcessor.postProcessMessage(message);
            ChannelHolder channelHolder = this.directReplyToContainer.getChannelHolder();
            correlationPostProcessor.getFuture().setChannelHolder(channelHolder);
            sendDirect(channelHolder.getChannel(), exchange, routingKey, message, correlationData);
        }
        RabbitConverterFuture<C> future = correlationPostProcessor.getFuture();
        future.startTimer();
        return future;
    }

    private void sendDirect(Channel channel, String exchange, String routingKey, Message message,
            CorrelationData correlationData) {
        message.getMessageProperties().setReplyTo(Address.AMQ_RABBITMQ_REPLY_TO);
        try {
            if (channel instanceof PublisherCallbackChannel) {
                this.template.addListener(channel);
            }
            this.template.doSend(channel, exchange, routingKey, message, this.template.isMandatoryFor(message),
                    correlationData);
        } catch (Exception e) {
            throw new AmqpException("Failed to send request", e);
        }
    }

    @Override
    public synchronized void start() {
        if (!this.running) {
            if (this.taskScheduler == null) {
                ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
                scheduler.setThreadNamePrefix(getBeanName() == null ? "asyncTemplate-" : (getBeanName() + "-"));
                scheduler.afterPropertiesSet();
                this.taskScheduler = scheduler;
            }
            if (this.container != null) {
                this.container.start();
            }
            if (this.directReplyToContainer != null) {
                this.directReplyToContainer.setTaskScheduler(this.taskScheduler);
                this.directReplyToContainer.start();
            }
        }
        this.running = true;
    }

    @Override
    public synchronized void stop() {
        if (this.running) {
            if (this.container != null) {
                this.container.stop();
            }
            if (this.directReplyToContainer != null) {
                this.directReplyToContainer.stop();
            }
            for (RabbitFuture<?> future : this.pending.values()) {
                future.setNackCause("AsyncRabbitTemplate was stopped while waiting for reply");
                future.cancel(true);
            }
        }
        this.running = false;
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }

    @Override
    public int getPhase() {
        return this.phase;
    }

    @Override
    public boolean isAutoStartup() {
        return this.autoStartup;
    }

    @Override
    public void stop(Runnable callback) {
        stop();
        callback.run();
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onMessage(Message message, Channel channel) {
        MessageProperties messageProperties = message.getMessageProperties();
        if (messageProperties != null) {
            String correlationId = messageProperties.getCorrelationId();
            if (StringUtils.hasText(correlationId)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("onMessage: " + message);
                }
                RabbitFuture<?> future = this.pending.remove(correlationId);
                if (future != null) {
                    if (future instanceof AsyncRabbitTemplate.RabbitConverterFuture) {
                        Object converted = this.template.getMessageConverter().fromMessage(message);
                        ((RabbitConverterFuture<Object>) future).set(converted);
                    } else {
                        ((RabbitMessageFuture) future).set(message);
                    }
                } else {
                    if (this.logger.isWarnEnabled()) {
                        this.logger.warn("No pending reply - perhaps timed out: " + message);
                    }
                }
            }
        }
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange,
            String routingKey) {
        MessageProperties messageProperties = message.getMessageProperties();
        String correlationId = messageProperties.getCorrelationId();
        if (StringUtils.hasText(correlationId)) {
            RabbitFuture<?> future = this.pending.remove(correlationId);
            if (future != null) {
                future.setException(new AmqpMessageReturnedException("Message returned", message, replyCode,
                        replyText, exchange, routingKey));
            } else {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("No pending reply - perhaps timed out? Message returned: " + message);
                }
            }
        }
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(
                    "Confirm: " + correlationData + ", ack=" + ack + (cause == null ? "" : (", cause: " + cause)));
        }
        String correlationId = correlationData.getId();
        if (correlationId != null) {
            RabbitFuture<?> future = this.pending.get(correlationId);
            if (future != null) {
                future.setNackCause(cause);
                ((SettableListenableFuture<Boolean>) future.getConfirm()).set(ack);
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Confirm: " + correlationData + ", ack=" + ack
                            + (cause == null ? "" : (", cause: " + cause))
                            + " no pending future - either canceled or the reply is already received");
                }
            }
        }
    }

    private String getOrSetCorrelationIdAndSetReplyTo(Message message) {
        String correlationId;
        MessageProperties messageProperties = message.getMessageProperties();
        Assert.notNull(messageProperties, "the message properties cannot be null");
        String currentCorrelationId = messageProperties.getCorrelationId();
        if (!StringUtils.hasText(currentCorrelationId)) {
            correlationId = UUID.randomUUID().toString();
            messageProperties.setCorrelationId(correlationId);
            Assert.isNull(messageProperties.getReplyTo(), "'replyTo' property must be null");
        } else {
            correlationId = currentCorrelationId;
        }
        messageProperties.setReplyTo(this.replyAddress);
        return correlationId;
    }

    @Override
    public String toString() {
        return this.beanName == null ? super.toString() : (this.getClass().getSimpleName() + ": " + this.beanName);
    }

    /**
     * Base class for {@link ListenableFuture}s returned by {@link AsyncRabbitTemplate}.
     * @since 1.6
     */
    public abstract class RabbitFuture<T> extends SettableListenableFuture<T> {

        private final String correlationId;

        private final Message requestMessage;

        private ScheduledFuture<?> timeoutTask;

        private volatile ListenableFuture<Boolean> confirm;

        private String nackCause;

        private ChannelHolder channelHolder;

        public RabbitFuture(String correlationId, Message requestMessage) {
            this.correlationId = correlationId;
            this.requestMessage = requestMessage;
        }

        void setChannelHolder(ChannelHolder channel) {
            this.channelHolder = channel;
        }

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            if (this.timeoutTask != null) {
                this.timeoutTask.cancel(true);
            }
            AsyncRabbitTemplate.this.pending.remove(this.correlationId);
            if (this.channelHolder != null && AsyncRabbitTemplate.this.directReplyToContainer != null) {
                AsyncRabbitTemplate.this.directReplyToContainer.releaseConsumerFor(this.channelHolder, false, null);
            }
            return super.cancel(mayInterruptIfRunning);
        }

        /**
         * When confirms are enabled contains a {@link ListenableFuture}
         * for the confirmation.
         * @return the future.
         */
        public ListenableFuture<Boolean> getConfirm() {
            return this.confirm;
        }

        void setConfirm(ListenableFuture<Boolean> confirm) {
            this.confirm = confirm;
        }

        /**
         * When confirms are enabled and a nack is received, contains
         * the cause for the nack, if any.
         * @return the cause.
         */
        public String getNackCause() {
            return this.nackCause;
        }

        void setNackCause(String nackCause) {
            this.nackCause = nackCause;
        }

        void startTimer() {
            if (AsyncRabbitTemplate.this.receiveTimeout > 0) {
                this.timeoutTask = AsyncRabbitTemplate.this.taskScheduler.schedule(new TimeoutTask(), // NOSONAR sync
                        new Date(System.currentTimeMillis() + AsyncRabbitTemplate.this.receiveTimeout));
            } else {
                this.timeoutTask = null;
            }
        }

        private class TimeoutTask implements Runnable {

            @Override
            public void run() {
                AsyncRabbitTemplate.this.pending.remove(RabbitFuture.this.correlationId);
                if (RabbitFuture.this.channelHolder != null
                        && AsyncRabbitTemplate.this.directReplyToContainer != null) {
                    AsyncRabbitTemplate.this.directReplyToContainer
                            .releaseConsumerFor(RabbitFuture.this.channelHolder, false, null);
                }
                setException(new AmqpReplyTimeoutException("Reply timed out", RabbitFuture.this.requestMessage));
            }

        }

    }

    /**
     * A {@link RabbitFuture} with a return type of {@link Message}.
     * @since 1.6
     */
    public class RabbitMessageFuture extends RabbitFuture<Message> implements ListenableFuture<Message> {

        public RabbitMessageFuture(String correlationId, Message requestMessage) {
            super(correlationId, requestMessage);
        }

    }

    /**
     * A {@link RabbitFuture} with a return type of the template's
     * generic parameter.
     * @since 1.6
     */
    public class RabbitConverterFuture<C> extends RabbitFuture<C> implements ListenableFuture<C> {

        public RabbitConverterFuture(String correlationId, Message requestMessage) {
            super(correlationId, requestMessage);
        }

    }

    private final class CorrelationMessagePostProcessor<C> implements MessagePostProcessor {

        private final MessagePostProcessor userPostProcessor;

        private final CorrelationData correlationData;

        private volatile RabbitConverterFuture<C> future;

        CorrelationMessagePostProcessor(MessagePostProcessor userPostProcessor, CorrelationData correlationData) {
            this.userPostProcessor = userPostProcessor;
            this.correlationData = correlationData;
        }

        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            Message messageToSend = message;
            if (this.userPostProcessor != null) {
                messageToSend = this.userPostProcessor.postProcessMessage(message);
            }
            String correlationId = getOrSetCorrelationIdAndSetReplyTo(messageToSend);
            this.future = new RabbitConverterFuture<C>(correlationId, message);
            if (this.correlationData != null && this.correlationData.getId() == null) {
                this.correlationData.setId(correlationId);
                this.future.setConfirm(new SettableListenableFuture<Boolean>());
            }
            AsyncRabbitTemplate.this.pending.put(correlationId, this.future);
            return messageToSend;
        }

        private RabbitConverterFuture<C> getFuture() {
            return this.future;
        }

    }

}