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

io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core.RabbitTemplate Maven / Gradle / Ivy

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

package io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpIllegalStateException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpRejectAndDontRequeueException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Address;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.AmqpMessageReturnedException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.CorrelationAwareMessagePostProcessor;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessageListener;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessagePostProcessor;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessageProperties;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Queue;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.ReceiveAndReplyCallback;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.ReceiveAndReplyMessageCallback;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.ReplyToAddressCallback;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.ChannelProxy;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.Connection;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.ConnectionFactory;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.PublisherCallbackChannelConnectionFactory;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.RabbitAccessor;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.RabbitResourceHolder;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.RabbitUtils;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.CorrelationData;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.ListenerContainerAware;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.PendingConfirm;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.PublisherCallbackChannel;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.RabbitExceptionTranslator;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.ValueExpression;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.converter.MessageConverter;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.converter.SimpleMessageConverter;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.postprocessor.MessagePostProcessorUtils;
import io.bitsensor.plugins.shaded.org.springframework.beans.BeansException;
import io.bitsensor.plugins.shaded.org.springframework.beans.factory.BeanFactory;
import io.bitsensor.plugins.shaded.org.springframework.beans.factory.BeanFactoryAware;
import io.bitsensor.plugins.shaded.org.springframework.context.expression.BeanFactoryResolver;
import io.bitsensor.plugins.shaded.org.springframework.context.expression.MapAccessor;
import io.bitsensor.plugins.shaded.org.springframework.expression.Expression;
import io.bitsensor.plugins.shaded.org.springframework.expression.spel.standard.SpelExpressionParser;
import io.bitsensor.plugins.shaded.org.springframework.expression.spel.support.StandardEvaluationContext;
import io.bitsensor.plugins.shaded.org.springframework.retry.RecoveryCallback;
import io.bitsensor.plugins.shaded.org.springframework.retry.RetryCallback;
import io.bitsensor.plugins.shaded.org.springframework.retry.RetryContext;
import io.bitsensor.plugins.shaded.org.springframework.retry.support.RetryTemplate;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.org.springframework.util.StringUtils;

import io.bitsensor.plugins.shaded.com.rabbitmq.client.AMQP;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.AMQP.BasicProperties;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.AMQP.Queue.DeclareOk;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.Channel;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.DefaultConsumer;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.Envelope;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.GetResponse;

/**
 * 

* Helper class that simplifies synchronous RabbitMQ access (sending and receiving messages). *

* *

* 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 channelTransacted=true as well). *

* *

* 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. *

* *

* 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 and 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. *

* * @author Mark Pollack * @author Mark Fisher * @author Dave Syer * @author Gary Russell * @author Artem Bilan * @author Ernest Sadykov * @since 1.0 */ public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware, RabbitOperations, MessageListener, ListenerContainerAware, PublisherCallbackChannel.Listener { 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 String DEFAULT_ENCODING = "UTF-8"; private static final SpelExpressionParser PARSER = new SpelExpressionParser(); private final ConcurrentMap publisherConfirmChannels = new ConcurrentHashMap(); private final Map replyHolder = new ConcurrentHashMap(); private final String uuid = UUID.randomUUID().toString(); private final AtomicInteger messageTagProvider = new AtomicInteger(); private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); private final ReplyToAddressCallback defaultReplyToAddressCallback = new ReplyToAddressCallback() { @Override public Address getReplyToAddress(Message request, Object reply) { return RabbitTemplate.this.getReplyToAddress(request); } }; private volatile String exchange = DEFAULT_EXCHANGE; private volatile String routingKey = DEFAULT_ROUTING_KEY; // The default queue name that will be used for synchronous receives. private volatile String queue; private volatile long receiveTimeout = 0; private volatile long replyTimeout = DEFAULT_REPLY_TIMEOUT; private volatile MessageConverter messageConverter = new SimpleMessageConverter(); private volatile MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter(); private volatile String encoding = DEFAULT_ENCODING; private volatile String replyAddress; private volatile ConfirmCallback confirmCallback; private volatile ReturnCallback returnCallback; private volatile Boolean confirmsOrReturnsCapable; private volatile Expression mandatoryExpression = new ValueExpression(false); private volatile String correlationKey = null; private volatile RetryTemplate retryTemplate; private volatile RecoveryCallback recoveryCallback; private volatile Expression sendConnectionFactorySelectorExpression; private volatile Expression receiveConnectionFactorySelectorExpression; private volatile boolean usingFastReplyTo; private volatile boolean evaluatedFastReplyTo; private volatile boolean useTemporaryReplyQueues; private volatile Collection beforePublishPostProcessors; private volatile Collection afterReceivePostProcessors; private volatile CorrelationDataPostProcessor correlationDataPostProcessor; private volatile boolean isListener; private volatile Expression userIdExpression; /** * Convenient constructor for use with setter injection. Don't forget to set the connection factory. */ public RabbitTemplate() { initDefaultStrategies(); } /** * Create a rabbit template with default strategies and settings. * * @param connectionFactory the connection factory to use */ public RabbitTemplate(ConnectionFactory connectionFactory) { this(); 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 "" * 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(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 */ public void setQueue(String queue) { this.queue = queue; } /** * The encoding to use when inter-converting between byte arrays and Strings in message properties. * * @param encoding the encoding to set */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * A queue 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 * http://www.rabbitmq.com/direct-reply-to.html * @deprecated - use #setReplyAddress(String replyAddress) * @param replyQueue the replyQueue to set */ @Deprecated public void setReplyQueue(Queue replyQueue) { setReplyAddress(replyQueue.getName()); } /** * 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 * http://www.rabbitmq.com/direct-reply-to.html *

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 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. *

* 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 io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.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 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; } 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 #returnCallback} has been provided. The result of expression 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; } /** * A SpEL {@link Expression} to evaluate * against each request message, if the provided {@link #getConnectionFactory()} * is an instance of {@link AbstractRoutingConnectionFactory}. *

* The result of this expression is used as {@code lookupKey} to get the target * {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory} * directly. *

* If this expression is evaluated to {@code null}, we fallback to the normal * {@link AbstractRoutingConnectionFactory} logic. *

* 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}. *

* 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}. *

* The result of this expression is used as {@code lookupKey} to get the target * {@link ConnectionFactory} from {@link AbstractRoutingConnectionFactory} * directly. *

* If this expression is evaluated to {@code null}, we fallback to the normal * {@link AbstractRoutingConnectionFactory} logic. *

* 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}. *

* 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, ConnectionFactory)} 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 */ 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)); } /** * @deprecated use {@link #setAfterReceivePostProcessors(MessagePostProcessor...)} * @param afterReceivePostProcessors the post processors. * @since 1.4.2 */ @Deprecated public void setAfterReceivePostProcessor(MessagePostProcessor... afterReceivePostProcessors) { setAfterReceivePostProcessors(afterReceivePostProcessors); } /** * 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 */ 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)); } /** * Set a {@link CorrelationDataPostProcessor} to be invoked before publishing a message. * Correlation data is used to correlate publisher confirms. * @param correlationDataPostProcessor the post processor. * @see #setConfirmCallback(ConfirmCallback) * @since 1.6.7 */ 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 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); } /** * 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 public Collection expectedQueueNames() { this.isListener = true; Collection replyQueue = null; if (this.replyAddress != null) { Address address = new Address(this.replyAddress); if ("".equals(address.getExchangeName())) { replyQueue = Collections.singletonList(address.getRoutingKey()); } else { logger.debug("Cannot verify reply queue because it has the form 'exchange/routingKey'"); } } 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. */ public Collection getUnconfirmed(long age) { Set unconfirmed = new HashSet(); synchronized (this.publisherConfirmChannels) { long cutoffTime = System.currentTimeMillis() - age; for (Channel channel : this.publisherConfirmChannels.keySet()) { Collection confirms = ((PublisherCallbackChannel) channel).expire(this, cutoffTime); for (PendingConfirm confirm : confirms) { unconfirmed.add(confirm.getCorrelationData()); } } } return unconfirmed.size() > 0 ? unconfirmed : null; } 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. *

* 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.error("'useTemporaryReplyQueues' is ignored when a 'replyAddress' is provided"); } else { return false; } } if (this.replyAddress == null || Address.AMQ_RABBITMQ_REPLY_TO.equals(this.replyAddress)) { try { execute(new ChannelCallback() { @Override public Void doInRabbit(Channel channel) throws Exception { channel.queueDeclarePassive(Address.AMQ_RABBITMQ_REPLY_TO); return null; } }); return true; } catch (Exception e) { if (this.replyAddress != null) { logger.error("Broker does not support fast replies via 'amq.rabbitmq.reply-to', temporary " + "queues will be used:" + e.getMessage() + "."); } else { if (logger.isDebugEnabled()) { logger.debug("Broker does not support fast replies via 'amq.rabbitmq.reply-to', temporary " + "queues will be used:" + e.getMessage() + "."); } } this.replyAddress = null; } } return false; } @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); } public void send(final String exchange, final String routingKey, final Message message, final CorrelationData correlationData) throws AmqpException { execute(new ChannelCallback() { @Override public Object doInRabbit(Channel channel) throws Exception { doSend(channel, exchange, routingKey, message, RabbitTemplate.this.returnCallback != null && 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); } @Deprecated public void correlationconvertAndSend(Object object, CorrelationData correlationData) throws AmqpException { this.correlationConvertAndSend(object, correlationData); } 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); } 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); } public void convertAndSend(String exchange, String routingKey, final Object object, 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); } 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); } public void convertAndSend(String exchange, String routingKey, final Object message, final MessagePostProcessor messagePostProcessor, CorrelationData correlationData) throws AmqpException { Message messageToSend = convertMessageIfNecessary(message); messageToSend = messagePostProcessor instanceof CorrelationAwareMessagePostProcessor ? ((CorrelationAwareMessagePostProcessor) messagePostProcessor) .postProcessMessage(messageToSend, correlationData) : messagePostProcessor.postProcessMessage(messageToSend); send(exchange, routingKey, messageToSend, correlationData); } @Override public Message receive() throws AmqpException { String queue = this.getRequiredQueue(); return this.receive(queue); } @Override 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 */ protected Message doReceiveNoWait(final String queueName) { return execute(new ChannelCallback() { @Override public Message doInRabbit(Channel channel) throws IOException { 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)); } @Override public Message receive(long timeoutMillis) throws AmqpException { String queue = getRequiredQueue(); if (timeoutMillis == 0) { return doReceiveNoWait(queue); } else { return receive(queue, timeoutMillis); } } @Override public Message receive(final String queueName, final long timeoutMillis) { return execute(new ChannelCallback() { @SuppressWarnings("deprecation") @Override public Message doInRabbit(Channel channel) throws Exception { io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer consumer = createQueueingConsumer(queueName, channel); io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer.Delivery delivery; if (timeoutMillis < 0) { delivery = consumer.nextDelivery(); } else { delivery = consumer.nextDelivery(timeoutMillis); } channel.basicCancel(consumer.getConsumerTag()); 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); } } }); } @Override public Object receiveAndConvert() throws AmqpException { return receiveAndConvert(this.getRequiredQueue()); } @Override public Object receiveAndConvert(String queueName) throws AmqpException { return receiveAndConvert(queueName, this.receiveTimeout); } @Override public Object receiveAndConvert(long timeoutMillis) throws AmqpException { return receiveAndConvert(this.getRequiredQueue(), timeoutMillis); } @Override 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 public boolean receiveAndReply(ReceiveAndReplyCallback callback) throws AmqpException { return this.receiveAndReply(this.getRequiredQueue(), callback); } @Override @SuppressWarnings("unchecked") public boolean receiveAndReply(final String queueName, ReceiveAndReplyCallback callback) throws AmqpException { return this.receiveAndReply(queueName, callback, (ReplyToAddressCallback) this.defaultReplyToAddressCallback); } @Override public boolean receiveAndReply(ReceiveAndReplyCallback callback, final String exchange, final String routingKey) throws AmqpException { return this.receiveAndReply(this.getRequiredQueue(), callback, exchange, routingKey); } @Override public boolean receiveAndReply(final String queueName, ReceiveAndReplyCallback callback, final String replyExchange, final String replyRoutingKey) throws AmqpException { return this.receiveAndReply(queueName, callback, new ReplyToAddressCallback() { @Override public Address getReplyToAddress(Message request, S reply) { return new Address(replyExchange, replyRoutingKey); } }); } @Override public boolean receiveAndReply(ReceiveAndReplyCallback callback, ReplyToAddressCallback replyToAddressCallback) throws AmqpException { return this.receiveAndReply(this.getRequiredQueue(), callback, replyToAddressCallback); } @Override public boolean receiveAndReply(String queueName, ReceiveAndReplyCallback callback, ReplyToAddressCallback replyToAddressCallback) throws AmqpException { return doReceiveAndReply(queueName, callback, replyToAddressCallback); } @SuppressWarnings("unchecked") private boolean doReceiveAndReply(final String queueName, final ReceiveAndReplyCallback callback, final ReplyToAddressCallback replyToAddressCallback) throws AmqpException { return this.execute(new ChannelCallback() { @SuppressWarnings("deprecation") @Override public Boolean doInRabbit(Channel channel) throws Exception { boolean channelTransacted = isChannelTransacted(); Message receiveMessage = null; boolean channelLocallyTransacted = isChannelLocallyTransacted(channel); 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 deliveryTag = response.getEnvelope().getDeliveryTag(); if (channelLocallyTransacted) { channel.basicAck(deliveryTag, false); } else if (channelTransacted) { // Not locally transacted but it is transacted so it could be // synchronized with an external transaction ConnectionFactoryUtils.registerDeliveryTag(getConnectionFactory(), channel, deliveryTag); } receiveMessage = buildMessageFromResponse(response); } } else { io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer consumer = createQueueingConsumer(queueName, channel); io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer.Delivery delivery; if (RabbitTemplate.this.receiveTimeout < 0) { delivery = consumer.nextDelivery(); } else { delivery = consumer.nextDelivery(RabbitTemplate.this.receiveTimeout); } channel.basicCancel(consumer.getConsumerTag()); if (delivery != null) { long deliveryTag = 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, deliveryTag); } else { channel.basicAck(deliveryTag, false); } receiveMessage = buildMessageFromDelivery(delivery); } } if (receiveMessage != null) { Object receive = receiveMessage; if (!(ReceiveAndReplyMessageCallback.class.isAssignableFrom(callback.getClass()))) { receive = RabbitTemplate.this.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) { Address replyTo = replyToAddressCallback.getReplyToAddress(receiveMessage, reply); Message replyMessage = RabbitTemplate.this.convertMessageIfNecessary(reply); MessageProperties receiveMessageProperties = receiveMessage.getMessageProperties(); MessageProperties replyMessageProperties = replyMessage.getMessageProperties(); Object correlation = RabbitTemplate.this.correlationKey == null ? receiveMessageProperties.getCorrelationId() : receiveMessageProperties.getHeaders().get(RabbitTemplate.this.correlationKey); if (RabbitTemplate.this.correlationKey == null || correlation == null) { // using standard correlationId property if (correlation == null) { String messageId = receiveMessageProperties.getMessageId(); if (messageId != null) { correlation = messageId.getBytes(RabbitTemplate.this.encoding); } } replyMessageProperties.setCorrelationId((byte[]) correlation); } else { replyMessageProperties.setHeader(RabbitTemplate.this.correlationKey, correlation); } // 'doSend()' takes care of 'channel.txCommit()'. RabbitTemplate.this.doSend( channel, replyTo.getExchangeName(), replyTo.getRoutingKey(), replyMessage, RabbitTemplate.this.returnCallback != null && RabbitTemplate.this.mandatoryExpression .getValue(RabbitTemplate.this.evaluationContext, replyMessage, Boolean.class), null); } else if (channelLocallyTransacted) { channel.txCommit(); } return true; } return false; } }, obtainTargetConnectionFactory(this.receiveConnectionFactorySelectorExpression, queueName)); } @Override public Message sendAndReceive(final Message message) throws AmqpException { return sendAndReceive(message, null); } public Message sendAndReceive(final Message message, CorrelationData correlationData) throws AmqpException { return doSendAndReceive(this.exchange, this.routingKey, message, correlationData); } @Override public Message sendAndReceive(final String routingKey, final Message message) throws AmqpException { return sendAndReceive(routingKey, message, null); } public Message sendAndReceive(final String routingKey, final Message message, CorrelationData correlationData) throws AmqpException { return doSendAndReceive(this.exchange, routingKey, message, correlationData); } @Override public Message sendAndReceive(final String exchange, final String routingKey, final Message message) throws AmqpException { return sendAndReceive(exchange, routingKey, message, null); } public Message sendAndReceive(final String exchange, final String routingKey, final Message message, CorrelationData correlationData) throws AmqpException { return doSendAndReceive(exchange, routingKey, message, correlationData); } @Override public Object convertSendAndReceive(final Object message) throws AmqpException { return convertSendAndReceive(message, (CorrelationData) null); } public Object convertSendAndReceive(final Object message, CorrelationData correlationData) throws AmqpException { return convertSendAndReceive(this.exchange, this.routingKey, message, null, correlationData); } @Override public Object convertSendAndReceive(final String routingKey, final Object message) throws AmqpException { return convertSendAndReceive(routingKey, message, (CorrelationData) null); } public Object convertSendAndReceive(final String routingKey, final Object message, CorrelationData correlationData) throws AmqpException { return convertSendAndReceive(this.exchange, routingKey, message, null, correlationData); } @Override public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message) throws AmqpException { return convertSendAndReceive(exchange, routingKey, message, (CorrelationData) null); } public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message, CorrelationData correlationData) throws AmqpException { return convertSendAndReceive(exchange, routingKey, message, null, correlationData); } @Override public Object convertSendAndReceive(final Object message, final MessagePostProcessor messagePostProcessor) throws AmqpException { return convertSendAndReceive(message, messagePostProcessor, null); } public Object convertSendAndReceive(final Object message, final MessagePostProcessor messagePostProcessor, CorrelationData correlationData) throws AmqpException { return convertSendAndReceive(this.exchange, this.routingKey, message, messagePostProcessor, correlationData); } @Override public Object convertSendAndReceive(final String routingKey, final Object message, final MessagePostProcessor messagePostProcessor) throws AmqpException { return convertSendAndReceive(routingKey, message, messagePostProcessor, null); } public Object convertSendAndReceive(final String routingKey, final Object message, final MessagePostProcessor messagePostProcessor, CorrelationData correlationData) throws AmqpException { return convertSendAndReceive(this.exchange, routingKey, message, messagePostProcessor, correlationData); } @Override public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message, final MessagePostProcessor messagePostProcessor) throws AmqpException { return convertSendAndReceive(exchange, routingKey, message, messagePostProcessor, null); } public Object convertSendAndReceive(final String exchange, final String routingKey, final Object message, final MessagePostProcessor messagePostProcessor, final CorrelationData correlationData) throws AmqpException { Message replyMessage = convertSendAndReceiveRaw(exchange, routingKey, message, messagePostProcessor, correlationData); if (replyMessage == null) { return null; } return this.getRequiredMessageConverter().fromMessage(replyMessage); } /** * 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 */ protected Message convertSendAndReceiveRaw(final String exchange, final String routingKey, final Object message, final MessagePostProcessor messagePostProcessor, final CorrelationData correlationData) { Message requestMessage = convertMessageIfNecessary(message); if (messagePostProcessor != null) { requestMessage = messagePostProcessor instanceof CorrelationAwareMessagePostProcessor ? ((CorrelationAwareMessagePostProcessor) messagePostProcessor) .postProcessMessage(requestMessage, correlationData) : messagePostProcessor.postProcessMessage(requestMessage); } Message replyMessage = doSendAndReceive(exchange, routingKey, requestMessage, correlationData); return replyMessage; } 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 */ protected Message doSendAndReceive(final String exchange, final String routingKey, final Message message, CorrelationData correlationData) { if (!this.evaluatedFastReplyTo) { synchronized (this) { if (!this.evaluatedFastReplyTo) { evaluateFastReplyTo(); } } } if (this.replyAddress == null || this.usingFastReplyTo) { return doSendAndReceiveWithTemporary(exchange, routingKey, message, correlationData); } else { return doSendAndReceiveWithFixed(exchange, routingKey, message, correlationData); } } protected Message doSendAndReceiveWithTemporary(final String exchange, final String routingKey, final Message message, final CorrelationData correlationData) { return this.execute(new ChannelCallback() { @Override public Message doInRabbit(Channel channel) throws Exception { final PendingReply pendingReply = new PendingReply(); String messageTag = String.valueOf(RabbitTemplate.this.messageTagProvider.incrementAndGet()); RabbitTemplate.this.replyHolder.put(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 DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { 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); } pendingReply.reply(reply); } }; channel.basicConsume(replyTo, true, consumerTag, true, true, null, consumer); Message reply = null; try { reply = exchangeMessages(exchange, routingKey, message, correlationData, channel, pendingReply, messageTag); } finally { RabbitTemplate.this.replyHolder.remove(messageTag); try { channel.basicCancel(consumerTag); } catch (Exception e) { } } return reply; } }, obtainTargetConnectionFactory(this.sendConnectionFactorySelectorExpression, message)); } 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 this.execute(new ChannelCallback() { @SuppressWarnings("deprecation") @Override public Message doInRabbit(Channel channel) throws Exception { final PendingReply pendingReply = new PendingReply(); String messageTag = String.valueOf(RabbitTemplate.this.messageTagProvider.incrementAndGet()); RabbitTemplate.this.replyHolder.put(messageTag, pendingReply); // 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(RabbitTemplate.this.replyAddress); String savedCorrelation = null; if (RabbitTemplate.this.correlationKey == null) { // using standard correlationId property byte[] correlationId = message.getMessageProperties().getCorrelationId(); if (correlationId != null) { savedCorrelation = new String(correlationId, RabbitTemplate.this.encoding); } } else { savedCorrelation = (String) message.getMessageProperties() .getHeaders().get(RabbitTemplate.this.correlationKey); } pendingReply.setSavedCorrelation(savedCorrelation); if (RabbitTemplate.this.correlationKey == null) { // using standard correlationId property message.getMessageProperties().setCorrelationId(messageTag .getBytes(RabbitTemplate.this.encoding)); } else { message.getMessageProperties().setHeader( RabbitTemplate.this.correlationKey, 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 { RabbitTemplate.this.replyHolder.remove(messageTag); } return reply; } }, obtainTargetConnectionFactory(this.sendConnectionFactorySelectorExpression, message)); } private Message exchangeMessages(final String exchange, final String routingKey, final Message message, final CorrelationData correlationData, Channel channel, final PendingReply pendingReply, String messageTag) throws Exception { Message reply; boolean mandatory = this.mandatoryExpression.getValue(this.evaluationContext, message, Boolean.class); 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); } return reply; } @Override public T execute(ChannelCallback action) { return execute(action, getConnectionFactory()); } @SuppressWarnings("unchecked") private T execute(final ChannelCallback action, final ConnectionFactory connectionFactory) { if (this.retryTemplate != null) { try { return this.retryTemplate.execute(new RetryCallback() { @Override public T doWithRetry(RetryContext context) throws Exception { return RabbitTemplate.this.doExecute(action, connectionFactory); } }, (RecoveryCallback) this.recoveryCallback); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw RabbitExceptionTranslator.convertRabbitAccessException(e); } } else { return doExecute(action, connectionFactory); } } private T doExecute(ChannelCallback action, ConnectionFactory connectionFactory) { Assert.notNull(action, "Callback object must not be null"); Channel channel; RabbitResourceHolder resourceHolder = null; Connection connection = null; if (isChannelTransacted()) { resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(connectionFactory, true); channel = resourceHolder.getChannel(); if (channel == null) { ConnectionFactoryUtils.releaseResources(resourceHolder); throw new IllegalStateException("Resource holder returned a null channel"); } } else { connection = connectionFactory.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"); } } catch (RuntimeException e) { RabbitUtils.closeConnection(connection); throw e; } } try { if (this.confirmsOrReturnsCapable == null) { determineConfirmsReturnsCapability(connectionFactory); } if (this.confirmsOrReturnsCapable) { addListener(channel); } if (logger.isDebugEnabled()) { logger.debug("Executing callback on RabbitMQ Channel: " + channel); } return action.doInRabbit(channel); } catch (Exception ex) { if (isChannelLocallyTransacted(channel)) { resourceHolder.rollbackAll(); } throw convertRabbitAccessException(ex); } finally { if (resourceHolder != null) { ConnectionFactoryUtils.releaseResources(resourceHolder); } else { RabbitUtils.closeChannel(channel); RabbitUtils.closeConnection(connection); } } } public void determineConfirmsReturnsCapability(ConnectionFactory connectionFactory) { if (connectionFactory instanceof PublisherCallbackChannelConnectionFactory) { PublisherCallbackChannelConnectionFactory pcccf = (PublisherCallbackChannelConnectionFactory) connectionFactory; this.confirmsOrReturnsCapable = pcccf.isPublisherConfirms() || pcccf.isPublisherReturns(); } else { this.confirmsOrReturnsCapable = Boolean.FALSE; } } /** * Send the given message to the specified exchange. * * @param channel The RabbitMQ Channel to operate within. * @param exchange The name of the RabbitMQ exchange to send to. * @param routingKey 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 */ protected void doSend(Channel channel, String exchange, String routingKey, Message message, boolean mandatory, CorrelationData correlationData) throws Exception { if (exchange == null) { // try to send to configured exchange exchange = this.exchange; } if (routingKey == null) { // try to send to configured routing key routingKey = this.routingKey; } if (logger.isDebugEnabled()) { logger.debug("Publishing message on exchange [" + exchange + "], routingKey = [" + routingKey + "]"); } Message messageToUse = message; MessageProperties messageProperties = messageToUse.getMessageProperties(); if (mandatory) { messageProperties.getHeaders().put(PublisherCallbackChannel.RETURN_CORRELATION_KEY, this.uuid); } if (this.beforePublishPostProcessors != null) { for (MessagePostProcessor processor : this.beforePublishPostProcessors) { messageToUse = processor instanceof CorrelationAwareMessagePostProcessor ? ((CorrelationAwareMessagePostProcessor) processor) .postProcessMessage(messageToUse, correlationData) : processor.postProcessMessage(messageToUse); } } 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); } } BasicProperties convertedMessageProperties = this.messagePropertiesConverter .fromMessageProperties(messageProperties, this.encoding); channel.basicPublish(exchange, routingKey, mandatory, convertedMessageProperties, messageToUse.getBody()); // Check if commit needed if (isChannelLocallyTransacted(channel)) { // Transacted channel created by this template -> commit. RabbitUtils.commitIfNecessary(channel); } } private void setupConfirm(Channel channel, Message message, CorrelationData correlationData) { if (this.confirmCallback != null && channel instanceof PublisherCallbackChannel) { PublisherCallbackChannel publisherCallbackChannel = (PublisherCallbackChannel) channel; correlationData = this.correlationDataPostProcessor != null ? this.correlationDataPostProcessor.postProcess(message, correlationData) : correlationData; publisherCallbackChannel.addPendingConfirm(this, channel.getNextPublishSeqNo(), new PendingConfirm(correlationData, System.currentTimeMillis())); } } /** * 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()); } @SuppressWarnings("deprecation") private Message buildMessageFromDelivery(io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer.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 = this.getMessageConverter(); if (converter == null) { throw new AmqpIllegalStateException( "No 'messageConverter' specified. Check configuration of RabbitTemplate."); } return converter; } private String getRequiredQueue() throws IllegalStateException { String name = this.queue; if (name == null) { throw new AmqpIllegalStateException("No 'queue' specified. Check configuration of RabbitTemplate."); } return name; } /** * Determine a reply-to Address for the given message. *

* The default implementation first checks the Rabbit Reply-To Address of the supplied request; if that is not * null it is returned; if it is null, then the configured default Exchange and * routing key are used to construct a reply-to Address. If the exchange property is also null, * then an {@link AmqpException} is thrown. * @param request the original incoming Rabbit message * @return the reply-to Address (never null) * @throws AmqpException if no {@link Address} can be determined * @see io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message#getMessageProperties() * @see io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.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; } private 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 pubsub 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()); } else { if (logger.isDebugEnabled()) { logger.warn("Confirm received but no callback available"); } } } @Override public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, BasicProperties properties, byte[] body) throws IOException { ReturnCallback returnCallback = this.returnCallback; if (returnCallback == 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) { returnCallback = new ReturnCallback() { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { pendingReply.returned(new AmqpMessageReturnedException("Message returned", message, replyCode, replyText, exchange, routingKey)); } }; } 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 (returnCallback != null) { properties.getHeaders().remove(PublisherCallbackChannel.RETURN_CORRELATION_KEY); MessageProperties messageProperties = this.messagePropertiesConverter.toMessageProperties( properties, null, this.encoding); Message returnedMessage = new Message(body, messageProperties); returnCallback.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 pubsub channel: " + channel + " from map, size now " + this.publisherConfirmChannels.size()); } } @Override public String getUUID() { return this.uuid; } @SuppressWarnings("deprecation") @Override public void onMessage(Message message) { try { String messageTag = null; if (this.correlationKey == null) { // using standard correlationId property byte[] correlationId = message.getMessageProperties().getCorrelationId(); if (correlationId != null) { messageTag = new String(correlationId, this.encoding); } } else { messageTag = (String) message.getMessageProperties() .getHeaders().get(this.correlationKey); } if (messageTag == null) { logger.error("No correlation header in reply"); return; } 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 { // Restore the inbound correlation data String savedCorrelation = pendingReply.getSavedCorrelation(); if (this.correlationKey == null) { if (savedCorrelation == null) { message.getMessageProperties().setCorrelationId(null); } else { message.getMessageProperties().setCorrelationId( savedCorrelation.getBytes(this.encoding)); } } 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); pendingReply.reply(message); if (logger.isDebugEnabled()) { logger.debug("Reply received for " + messageTag); if (savedReplyTo != null) { logger.debug("Restored replyTo to " + savedReplyTo); } } } } catch (UnsupportedEncodingException e) { throw new AmqpIllegalStateException("Invalid Character Set:" + this.encoding, e); } } @SuppressWarnings("deprecation") private io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer createQueueingConsumer(final String queueName, Channel channel) throws Exception { channel.basicQos(1); final CountDownLatch latch = new CountDownLatch(1); io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer consumer = new io.bitsensor.plugins.shaded.com.rabbitmq.client.QueueingConsumer(channel) { @Override public void handleCancel(String consumerTag) throws IOException { super.handleCancel(consumerTag); latch.countDown(); } @Override public void handleConsumeOk(String consumerTag) { super.handleConsumeOk(consumerTag); latch.countDown(); } }; channel.basicConsume(queueName, consumer); if (!latch.await(10, TimeUnit.SECONDS)) { if (channel instanceof ChannelProxy) { ((ChannelProxy) channel).getTargetChannel().close(); } throw new AmqpException("Blocking receive, consumer failed to consume"); } return consumer; } private static class PendingReply { private volatile String savedReplyTo; private volatile String savedCorrelation; private final BlockingQueue queue = new ArrayBlockingQueue(1); public String getSavedReplyTo() { return this.savedReplyTo; } public void setSavedReplyTo(String savedReplyTo) { this.savedReplyTo = savedReplyTo; } public String getSavedCorrelation() { return this.savedCorrelation; } public void setSavedCorrelation(String savedCorrelation) { this.savedCorrelation = savedCorrelation; } public Message get() throws InterruptedException { Object reply = this.queue.take(); return processReply(reply); } public Message get(long timeout, TimeUnit unit) throws InterruptedException { Object reply = this.queue.poll(timeout, unit); return reply == null ? null : processReply(reply); } private Message processReply(Object reply) { if (reply instanceof Message) { return (Message) reply; } else if (reply instanceof AmqpException) { throw (AmqpException) reply; } else { throw new AmqpException("Unexpected reply type " + reply.getClass().getName()); } } public void reply(Message reply) { this.queue.add(reply); } public void returned(AmqpMessageReturnedException e) { this.queue.add(e); } } 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(CorrelationData correlationData, boolean ack, String cause); } 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); } }