Java tutorial
/* * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.amqp.rabbit.listener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.stream.Collectors; import org.aopalliance.aop.Advice; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.amqp.AmqpConnectException; import org.springframework.amqp.AmqpIOException; import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.ImmediateAcknowledgeAmqpException; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageListener; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.batch.BatchingStrategy; import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy; import org.springframework.amqp.rabbit.connection.Connection; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils; import org.springframework.amqp.rabbit.connection.RabbitAccessor; import org.springframework.amqp.rabbit.connection.RabbitResourceHolder; import org.springframework.amqp.rabbit.connection.RabbitUtils; import org.springframework.amqp.rabbit.connection.RoutingConnectionFactory; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.amqp.rabbit.listener.exception.FatalListenerExecutionException; import org.springframework.amqp.rabbit.listener.exception.FatalListenerStartupException; import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter; import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; import org.springframework.amqp.rabbit.support.MessagePropertiesConverter; import org.springframework.amqp.support.ConditionalExceptionLogger; import org.springframework.amqp.support.ConsumerTagStrategy; import org.springframework.amqp.support.postprocessor.MessagePostProcessorUtils; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.lang.Nullable; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttribute; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.ErrorHandler; import org.springframework.util.StringUtils; import org.springframework.util.backoff.BackOff; import org.springframework.util.backoff.FixedBackOff; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ShutdownSignalException; /** * @author Mark Pollack * @author Mark Fisher * @author Dave Syer * @author James Carr * @author Gary Russell * @author Alex Panchenko * @author Johno Crawford * @author Arnaud Cogolugnes * @author Artem Bilan * @author Mohammad Hewedy */ public abstract class AbstractMessageListenerContainer extends RabbitAccessor implements MessageListenerContainer, ApplicationContextAware, BeanNameAware, DisposableBean, ApplicationEventPublisherAware { private static final String UNCHECKED = "unchecked"; static final int DEFAULT_FAILED_DECLARATION_RETRY_INTERVAL = 5000; public static final boolean DEFAULT_DEBATCHING_ENABLED = true; public static final int DEFAULT_PREFETCH_COUNT = 250; /** * The default recovery interval: 5000 ms = 5 seconds. */ public static final long DEFAULT_RECOVERY_INTERVAL = 5000; public static final long DEFAULT_SHUTDOWN_TIMEOUT = 5000; private final ContainerDelegate delegate = this::actualInvokeListener; protected final Object consumersMonitor = new Object(); //NOSONAR private final Map<String, Object> consumerArgs = new HashMap<>(); private ContainerDelegate proxy = this.delegate; private long shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT; private ApplicationEventPublisher applicationEventPublisher; @Nullable private PlatformTransactionManager transactionManager; private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); @Nullable private String beanName; private Executor taskExecutor = new SimpleAsyncTaskExecutor(); private boolean taskExecutorSet; private BackOff recoveryBackOff = new FixedBackOff(DEFAULT_RECOVERY_INTERVAL, FixedBackOff.UNLIMITED_ATTEMPTS); private MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter(); private AmqpAdmin amqpAdmin; private boolean missingQueuesFatal = true; private boolean missingQueuesFatalSet; private boolean possibleAuthenticationFailureFatal = true; private boolean possibleAuthenticationFailureFatalSet; private boolean autoDeclare = true; private boolean mismatchedQueuesFatal = false; private long failedDeclarationRetryInterval = DEFAULT_FAILED_DECLARATION_RETRY_INTERVAL; private boolean autoStartup = true; private int phase = Integer.MAX_VALUE; private volatile boolean active = false; private volatile boolean running = false; private final Object lifecycleMonitor = new Object(); private volatile List<Queue> queues = new CopyOnWriteArrayList<>(); private ErrorHandler errorHandler = new ConditionalRejectingErrorHandler(); private boolean exposeListenerChannel = true; private volatile MessageListener messageListener; private volatile AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO; private volatile boolean deBatchingEnabled = DEFAULT_DEBATCHING_ENABLED; private volatile boolean initialized; private Collection<MessagePostProcessor> afterReceivePostProcessors; private volatile ApplicationContext applicationContext; private String listenerId; private Advice[] adviceChain = new Advice[0]; @Nullable private ConsumerTagStrategy consumerTagStrategy; private volatile boolean exclusive; private volatile boolean noLocal; private volatile boolean defaultRequeueRejected = true; private volatile int prefetchCount = DEFAULT_PREFETCH_COUNT; private long idleEventInterval; private volatile long lastReceive = System.currentTimeMillis(); private boolean statefulRetryFatalWithNullMessageId = true; private ConditionalExceptionLogger exclusiveConsumerExceptionLogger = new DefaultExclusiveConsumerLogger(); private boolean alwaysRequeueWithTxManagerRollback; private String lookupKeyQualifier = ""; private boolean forceCloseChannel = true; private String errorHandlerLoggerName = getClass().getName(); private BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(0, 0, 0L); private volatile boolean lazyLoad; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } protected ApplicationEventPublisher getApplicationEventPublisher() { return this.applicationEventPublisher; } /** * <p> * Flag controlling the behaviour of the container with respect to message acknowledgement. The most common usage is * to let the container handle the acknowledgements (so the listener doesn't need to know about the channel or the * message). * <p> * Set to {@link AcknowledgeMode#MANUAL} if the listener will send the acknowledgements itself using * {@link Channel#basicAck(long, boolean)}. Manual acks are consistent with either a transactional or * non-transactional channel, but if you are doing no other work on the channel at the same other than receiving a * single message then the transaction is probably unnecessary. * <p> * Set to {@link AcknowledgeMode#NONE} to tell the broker not to expect any acknowledgements, and it will assume all * messages are acknowledged as soon as they are sent (this is "autoack" in native Rabbit broker terms). If * {@link AcknowledgeMode#NONE} then the channel cannot be transactional (so the container will fail on start up if * that flag is accidentally set). * @param acknowledgeMode the acknowledge mode to set. Defaults to {@link AcknowledgeMode#AUTO} * @see AcknowledgeMode */ public final void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) { this.acknowledgeMode = acknowledgeMode; } /** * @return the acknowledgeMode */ public AcknowledgeMode getAcknowledgeMode() { return this.acknowledgeMode; } /** * Set the name of the queue(s) to receive messages from. * @param queueName the desired queueName(s) (can not be <code>null</code>) */ public void setQueueNames(String... queueName) { Assert.noNullElements(queueName, "Queue name(s) cannot be null"); setQueues(Arrays.stream(queueName).map(Queue::new).toArray(Queue[]::new)); } /** * Set the name of the queue(s) to receive messages from. * @param queues the desired queue(s) (can not be <code>null</code>) */ public final void setQueues(Queue... queues) { Assert.notNull(queues, "'queues' cannot be null"); Assert.noNullElements(queues, "'queues' cannot contain null elements"); if (isRunning()) { for (Queue queue : queues) { Assert.isTrue(StringUtils.hasText(queue.getName()), "Cannot add broker-named queues dynamically"); } } this.queues = new CopyOnWriteArrayList<>(queues); } /** * @return the name of the queues to receive messages from. */ public String[] getQueueNames() { return queuesToNames().toArray(new String[0]); } protected Set<String> getQueueNamesAsSet() { return new HashSet<>(queuesToNames()); } /** * Returns a map of current queue names to the Queue object; allows the * determination of a changed broker-named queue. * @return the map. * @since 2.1 */ protected Map<String, Queue> getQueueNamesToQueues() { return this.queues.stream().collect(Collectors.toMap(Queue::getActualName, q -> q)); } private List<String> queuesToNames() { return this.queues.stream().map(Queue::getActualName).collect(Collectors.toList()); } /** * Add queue(s) to this container's list of queues. * @param queueNames The queue(s) to add. */ public void addQueueNames(String... queueNames) { Assert.notNull(queueNames, "'queueNames' cannot be null"); Assert.noNullElements(queueNames, "'queueNames' cannot contain null elements"); addQueues(Arrays.stream(queueNames).map(Queue::new).toArray(Queue[]::new)); } /** * Add queue(s) to this container's list of queues. * @param queues The queue(s) to add. */ public void addQueues(Queue... queues) { Assert.notNull(queues, "'queues' cannot be null"); Assert.noNullElements(queues, "'queues' cannot contain null elements"); if (isRunning()) { for (Queue queue : queues) { Assert.hasText(queue.getName(), "Cannot add broker-named queues dynamically"); } } this.queues.addAll(Arrays.asList(queues)); } /** * Remove queue(s) from this container's list of queues. * @param queueNames The queue(s) to remove. * @return the boolean result of removal on the target {@code queueNames} List. */ public boolean removeQueueNames(String... queueNames) { Assert.notNull(queueNames, "'queueNames' cannot be null"); Assert.noNullElements(queueNames, "'queueNames' cannot contain null elements"); if (this.queues.size() > 0) { Set<String> toRemove = new HashSet<>(Arrays.asList(queueNames)); return this.queues.removeIf(q -> toRemove.contains(q.getActualName())); } return false; } /** * Remove queue(s) from this container's list of queues. * @param queues The queue(s) to remove. * @return the boolean result of removal on the target {@code queueNames} List. */ public boolean removeQueues(Queue... queues) { Assert.notNull(queues, "'queues' cannot be null"); Assert.noNullElements(queues, "'queues' cannot contain null elements"); return removeQueueNames(Arrays.stream(queues).map(Queue::getActualName).toArray(String[]::new)); } /** * @return whether to expose the listener {@link Channel} to a registered {@link ChannelAwareMessageListener}. */ public boolean isExposeListenerChannel() { return this.exposeListenerChannel; } /** * Set whether to expose the listener Rabbit Channel to a registered {@link ChannelAwareMessageListener} as well as * to {@link org.springframework.amqp.rabbit.core.RabbitTemplate} calls. * <p> * Default is "true", reusing the listener's {@link Channel}. Turn this off to expose a fresh Rabbit Channel fetched * from the same underlying Rabbit {@link Connection} instead. * <p> * Note that Channels managed by an external transaction manager will always get exposed to * {@link org.springframework.amqp.rabbit.core.RabbitTemplate} calls. So in terms of RabbitTemplate exposure, this * setting only affects locally transacted Channels. * * @param exposeListenerChannel true to expose the channel. * * @see ChannelAwareMessageListener */ public void setExposeListenerChannel(boolean exposeListenerChannel) { this.exposeListenerChannel = exposeListenerChannel; } /** * Set the {@link MessageListener}. * @param messageListener the listener. * @since 2.0 */ public void setMessageListener(MessageListener messageListener) { this.messageListener = messageListener; } /** * Check the given message listener, throwing an exception if it does not correspond to a supported listener type. * <p> * Only a Spring {@link MessageListener} object will be accepted. * @param listener the message listener object to check * @throws IllegalArgumentException if the supplied listener is not a MessageListener * @see MessageListener */ protected void checkMessageListener(Object listener) { if (!(listener instanceof MessageListener)) { throw new IllegalArgumentException( "Message listener needs to be of type [" + MessageListener.class.getName() + "] or [" + ChannelAwareMessageListener.class.getName() + "]"); } } /** * @return The message listener object to register. */ public Object getMessageListener() { return this.messageListener; } /** * Set an ErrorHandler to be invoked in case of any uncaught exceptions thrown while processing a Message. By * default there will be <b>no</b> ErrorHandler so that error-level logging is the only result. * * @param errorHandler The error handler. */ public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } /** * Determine whether or not the container should de-batch batched * messages (true) or call the listener with the batch (false). Default: true. * @param deBatchingEnabled the deBatchingEnabled to set. * @see #setBatchingStrategy(BatchingStrategy) */ public void setDeBatchingEnabled(boolean deBatchingEnabled) { this.deBatchingEnabled = deBatchingEnabled; } protected boolean isDeBatchingEnabled() { return this.deBatchingEnabled; } /** * Public setter for the {@link Advice} to apply to listener executions. * <p> * If a {code #setTransactionManager(PlatformTransactionManager) transactionManager} is provided as well, then * separate advice is created for the transaction and applied first in the chain. In that case the advice chain * provided here should not contain a transaction interceptor (otherwise two transactions would be be applied). * @param adviceChain the advice chain to set */ public void setAdviceChain(Advice... adviceChain) { Assert.notNull(adviceChain, "'adviceChain' cannot be null"); this.adviceChain = Arrays.copyOf(adviceChain, adviceChain.length); } protected Advice[] getAdviceChain() { return this.adviceChain; // NOSONAR direct access } /** * Set {@link MessagePostProcessor}s that will be applied after message reception, before * invoking the {@link MessageListener}. Often used to decompress data. Processors are invoked in order, * depending on {@code PriorityOrder}, {@code Order} and finally unordered. * @param afterReceivePostProcessors the post processor. * @since 1.4.2 * @see #addAfterReceivePostProcessors(MessagePostProcessor...) */ public void setAfterReceivePostProcessors(MessagePostProcessor... afterReceivePostProcessors) { Assert.notNull(afterReceivePostProcessors, "'afterReceivePostProcessors' cannot be null"); Assert.noNullElements(afterReceivePostProcessors, "'afterReceivePostProcessors' cannot have null elements"); this.afterReceivePostProcessors = MessagePostProcessorUtils.sort(Arrays.asList(afterReceivePostProcessors)); } /** * Add {@link MessagePostProcessor}s that will be applied after message reception, before * invoking the {@link MessageListener}. Often used to decompress data. Processors are invoked in order, * depending on {@code PriorityOrder}, {@code Order} and finally unordered. * <p> * In contrast to {@link #setAfterReceivePostProcessors(MessagePostProcessor...)}, this * method does not override the previously added afterReceivePostProcessors. * @param postprocessors the post processor. * @since 2.1.4 */ public void addAfterReceivePostProcessors(MessagePostProcessor... postprocessors) { Assert.notNull(postprocessors, "'afterReceivePostProcessors' cannot be null"); if (this.afterReceivePostProcessors == null) { this.afterReceivePostProcessors = new ArrayList<>(); } this.afterReceivePostProcessors.addAll(Arrays.asList(postprocessors)); this.afterReceivePostProcessors = MessagePostProcessorUtils.sort(this.afterReceivePostProcessors); } /** * Remove the provided {@link MessagePostProcessor} from the {@link #afterReceivePostProcessors} list. * @param afterReceivePostProcessor the MessagePostProcessor to remove. * @return the boolean if the provided post processor has been removed. * @since 2.1.4 * @see #addAfterReceivePostProcessors(MessagePostProcessor...) */ public boolean removeAfterReceivePostProcessor(MessagePostProcessor afterReceivePostProcessor) { Assert.notNull(afterReceivePostProcessor, "'afterReceivePostProcessor' cannot be null"); if (this.afterReceivePostProcessors != null) { return this.afterReceivePostProcessors.remove(afterReceivePostProcessor); } return false; } /** * Set whether to automatically start the container after initialization. * <p> * Default is "true"; set this to "false" to allow for manual startup through the {@link #start()} method. * * @param autoStartup true for auto startup. */ public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } @Override public boolean isAutoStartup() { return this.autoStartup; } /** * Specify the phase in which this container should be started and stopped. The startup order proceeds from lowest * to highest, and the shutdown order is the reverse of that. By default this value is Integer.MAX_VALUE meaning * that this container starts as late as possible and stops as soon as possible. * * @param phase The phase. */ public void setPhase(int phase) { this.phase = phase; } /** * @return The phase in which this container will be started and stopped. */ @Override public int getPhase() { return this.phase; } @Override public void setBeanName(String beanName) { this.beanName = beanName; } /** * @return The bean name that this listener container has been assigned in its containing bean factory, if any. */ @Nullable protected final String getBeanName() { return this.beanName; } protected final ApplicationContext getApplicationContext() { return this.applicationContext; } @Override public final void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override public ConnectionFactory getConnectionFactory() { ConnectionFactory connectionFactory = super.getConnectionFactory(); if (connectionFactory instanceof RoutingConnectionFactory) { ConnectionFactory targetConnectionFactory = ((RoutingConnectionFactory) connectionFactory) .getTargetConnectionFactory(getRoutingLookupKey()); // NOSONAR never null if (targetConnectionFactory != null) { return targetConnectionFactory; } } return connectionFactory; } /** * Set a qualifier that will prefix the connection factory lookup key; default none. * @param lookupKeyQualifier the qualifier * @since 1.6.9 * @see #getRoutingLookupKey() */ public void setLookupKeyQualifier(String lookupKeyQualifier) { this.lookupKeyQualifier = lookupKeyQualifier; } /** * Force close the channel if the consumer threads don't respond to a shutdown. * @return true to force close. * @since 1.7.4 */ protected boolean isForceCloseChannel() { return this.forceCloseChannel; } /** * Set to true to force close the channel if the consumer threads don't respond to a * shutdown. Default: true (since 2.0). * @param forceCloseChannel true to force close. * @since 1.7.4 */ public void setForceCloseChannel(boolean forceCloseChannel) { this.forceCloseChannel = forceCloseChannel; } /** * Return the lookup key if the connection factory is a * {@link RoutingConnectionFactory}; null otherwise. The routing key is the * comma-delimited list of queue names with all spaces removed and bracketed by [...], * optionally prefixed by a qualifier, e.g. "foo[...]". * @return the key or null. * @since 1.6.9 * @see #setLookupKeyQualifier(String) */ @Nullable protected String getRoutingLookupKey() { return super.getConnectionFactory() instanceof RoutingConnectionFactory ? this.lookupKeyQualifier + "[" + this.queues.stream().map(Queue::getName).collect(Collectors.joining(",")) + "]" : null; } /** * Return the (@link RoutingConnectionFactory} if the connection factory is a * {@link RoutingConnectionFactory}; null otherwise. * @return the {@link RoutingConnectionFactory} or null. * @since 1.6.9 */ @Nullable protected RoutingConnectionFactory getRoutingConnectionFactory() { return super.getConnectionFactory() instanceof RoutingConnectionFactory ? (RoutingConnectionFactory) super.getConnectionFactory() : null; } /** * The 'id' attribute of the listener. * @return the id (or the container bean name if no id set). */ @Nullable public String getListenerId() { return this.listenerId != null ? this.listenerId : this.beanName; } public void setListenerId(String listenerId) { this.listenerId = listenerId; } /** * Set the implementation of {@link ConsumerTagStrategy} to generate consumer tags. * By default, the RabbitMQ server generates consumer tags. * @param consumerTagStrategy the consumerTagStrategy to set. * @since 1.4.5 */ public void setConsumerTagStrategy(ConsumerTagStrategy consumerTagStrategy) { this.consumerTagStrategy = consumerTagStrategy; } /** * Return the consumer tag strategy to use. * @return the strategy. * @since 2.0 */ @Nullable protected ConsumerTagStrategy getConsumerTagStrategy() { return this.consumerTagStrategy; } /** * Set consumer arguments. * @param args the arguments. * @since 1.3 */ public void setConsumerArguments(Map<String, Object> args) { synchronized (this.consumersMonitor) { this.consumerArgs.clear(); this.consumerArgs.putAll(args); } } /** * Return the consumer arguments. * @return the arguments. * @since 2.0 */ protected Map<String, Object> getConsumerArguments() { return this.consumerArgs; } /** * Set to true for an exclusive consumer. * @param exclusive true for an exclusive consumer. */ public void setExclusive(boolean exclusive) { this.exclusive = exclusive; } /** * Return whether the consumers should be exclusive. * @return true for exclusive consumers. */ protected boolean isExclusive() { return this.exclusive; } /** * Set to true for an no-local consumer. * @param noLocal true for an no-local consumer. */ public void setNoLocal(boolean noLocal) { this.noLocal = noLocal; } /** * Return whether the consumers should be no-local. * @return true for no-local consumers. */ protected boolean isNoLocal() { return this.noLocal; } /** * Set the default behavior when a message is rejected, for example because the listener * threw an exception. When true, messages will be requeued, when false, they will not. For * versions of Rabbit that support dead-lettering, the message must not be requeued in order * to be sent to the dead letter exchange. Setting to false causes all rejections to not * be requeued. When true, the default can be overridden by the listener throwing an * {@link AmqpRejectAndDontRequeueException}. Default true. * @param defaultRequeueRejected true to reject by default. */ public void setDefaultRequeueRejected(boolean defaultRequeueRejected) { this.defaultRequeueRejected = defaultRequeueRejected; } /** * Return the default requeue rejected. * @return the boolean. * @since 2.0 * @see #setDefaultRequeueRejected(boolean) */ protected boolean isDefaultRequeueRejected() { return this.defaultRequeueRejected; } /** * Tell the broker how many messages to send to each consumer in a single request. * Often this can be set quite high to improve throughput. * @param prefetchCount the prefetch count */ public void setPrefetchCount(int prefetchCount) { this.prefetchCount = prefetchCount; } /** * Return the prefetch count. * @return the count. * @since 2.0 */ protected int getPrefetchCount() { return this.prefetchCount; } /** * The time to wait for workers in milliseconds after the container is stopped. If any * workers are active when the shutdown signal comes they will be allowed to finish * processing as long as they can finish within this timeout. Defaults * to 5 seconds. * @param shutdownTimeout the shutdown timeout to set */ public void setShutdownTimeout(long shutdownTimeout) { this.shutdownTimeout = shutdownTimeout; } protected long getShutdownTimeout() { return this.shutdownTimeout; } /** * How often to emit {@link ListenerContainerIdleEvent}s in milliseconds. * @param idleEventInterval the interval. */ public void setIdleEventInterval(long idleEventInterval) { this.idleEventInterval = idleEventInterval; } protected long getIdleEventInterval() { return this.idleEventInterval; } /** * Get the time the last message was received - initialized to container start * time. * @return the time. */ protected long getLastReceive() { return this.lastReceive; } /** * Set the transaction manager to use. * @param transactionManager the transaction manager. */ public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } @Nullable protected PlatformTransactionManager getTransactionManager() { return this.transactionManager; } /** * Set the transaction attribute to use when using an external transaction manager. * @param transactionAttribute the transaction attribute to set */ public void setTransactionAttribute(TransactionAttribute transactionAttribute) { Assert.notNull(transactionAttribute, "'transactionAttribute' cannot be null"); this.transactionAttribute = transactionAttribute; } protected TransactionAttribute getTransactionAttribute() { return this.transactionAttribute; } /** * Set a task executor for the container - used to create the consumers not at * runtime. * @param taskExecutor the task executor. */ public void setTaskExecutor(Executor taskExecutor) { Assert.notNull(taskExecutor, "'taskExecutor' cannot be null"); this.taskExecutor = taskExecutor; this.taskExecutorSet = true; } protected Executor getTaskExecutor() { return this.taskExecutor; } /** * Specify the interval between recovery attempts, in <b>milliseconds</b>. * The default is 5000 ms, that is, 5 seconds. * @param recoveryInterval The recovery interval. */ public void setRecoveryInterval(long recoveryInterval) { this.recoveryBackOff = new FixedBackOff(recoveryInterval, FixedBackOff.UNLIMITED_ATTEMPTS); } /** * Specify the {@link BackOff} for interval between recovery attempts. * The default is 5000 ms, that is, 5 seconds. * With the {@link BackOff} you can supply the {@code maxAttempts} for recovery before * the {@link #stop()} will be performed. * @param recoveryBackOff The BackOff to recover. * @since 1.5 */ public void setRecoveryBackOff(BackOff recoveryBackOff) { Assert.notNull(recoveryBackOff, "'recoveryBackOff' must not be null."); this.recoveryBackOff = recoveryBackOff; } protected BackOff getRecoveryBackOff() { return this.recoveryBackOff; } /** * Set the {@link MessagePropertiesConverter} for this listener container. * @param messagePropertiesConverter The properties converter. */ public void setMessagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) { Assert.notNull(messagePropertiesConverter, "messagePropertiesConverter must not be null"); this.messagePropertiesConverter = messagePropertiesConverter; } protected MessagePropertiesConverter getMessagePropertiesConverter() { return this.messagePropertiesConverter; } @Nullable protected AmqpAdmin getAmqpAdmin() { return this.amqpAdmin; } /** * Set the {@link AmqpAdmin}, used to declare any auto-delete queues, bindings * etc when the container is started. Only needed if those queues use conditional * declaration (have a 'declared-by' attribute). If not specified, an internal * admin will be used which will attempt to declare all elements not having a * 'declared-by' attribute. * @param amqpAdmin the AmqpAdmin to use * @since 2.1 */ public void setAmqpAdmin(AmqpAdmin amqpAdmin) { this.amqpAdmin = amqpAdmin; } /** * If all of the configured queue(s) are not available on the broker, this setting * determines whether the condition is fatal. When true, and * the queues are missing during startup, the context refresh() will fail. * <p> When false, the condition is not considered fatal and the container will * continue to attempt to start the consumers. * @param missingQueuesFatal the missingQueuesFatal to set. * @since 1.3.5 * @see #setAutoDeclare(boolean) */ public void setMissingQueuesFatal(boolean missingQueuesFatal) { this.missingQueuesFatal = missingQueuesFatal; this.missingQueuesFatalSet = true; } protected boolean isMissingQueuesFatal() { return this.missingQueuesFatal; } protected boolean isMissingQueuesFatalSet() { return this.missingQueuesFatalSet; } /** * Prevent the container from starting if any of the queues defined in the context have * mismatched arguments (TTL etc). Default false. * @param mismatchedQueuesFatal true to fail initialization when this condition occurs. * @since 1.6 */ public void setMismatchedQueuesFatal(boolean mismatchedQueuesFatal) { this.mismatchedQueuesFatal = mismatchedQueuesFatal; } protected boolean isMismatchedQueuesFatal() { return this.mismatchedQueuesFatal; } public void setPossibleAuthenticationFailureFatal(boolean possibleAuthenticationFailureFatal) { this.possibleAuthenticationFailureFatal = possibleAuthenticationFailureFatal; this.possibleAuthenticationFailureFatalSet = true; } public boolean isPossibleAuthenticationFailureFatal() { return this.possibleAuthenticationFailureFatal; } protected boolean isPossibleAuthenticationFailureFatalSet() { return this.possibleAuthenticationFailureFatalSet; } /** * Set to true to automatically declare elements (queues, exchanges, bindings) * in the application context during container start(). * @param autoDeclare the boolean flag to indicate an declaration operation. * @since 1.4 * @see #redeclareElementsIfNecessary */ public void setAutoDeclare(boolean autoDeclare) { this.autoDeclare = autoDeclare; } protected boolean isAutoDeclare() { return this.autoDeclare; } /** * Set the interval between passive queue declaration attempts in milliseconds. * @param failedDeclarationRetryInterval the interval, default 5000. * @since 1.3.9 */ public void setFailedDeclarationRetryInterval(long failedDeclarationRetryInterval) { this.failedDeclarationRetryInterval = failedDeclarationRetryInterval; } protected long getFailedDeclarationRetryInterval() { return this.failedDeclarationRetryInterval; } protected boolean isStatefulRetryFatalWithNullMessageId() { return this.statefulRetryFatalWithNullMessageId; } /** * Set whether a message with a null messageId is fatal for the consumer * when using stateful retry. When false, instead of stopping the consumer, * the message is rejected and not requeued - it will be discarded or routed * to the dead letter queue, if so configured. Default true. * @param statefulRetryFatalWithNullMessageId true for fatal. * @since 2.0 */ public void setStatefulRetryFatalWithNullMessageId(boolean statefulRetryFatalWithNullMessageId) { this.statefulRetryFatalWithNullMessageId = statefulRetryFatalWithNullMessageId; } /** * Set a {@link ConditionalExceptionLogger} for logging exclusive consumer failures. The * default is to log such failures at WARN level. * @param exclusiveConsumerExceptionLogger the conditional exception logger. * @since 1.5 */ public void setExclusiveConsumerExceptionLogger(ConditionalExceptionLogger exclusiveConsumerExceptionLogger) { this.exclusiveConsumerExceptionLogger = exclusiveConsumerExceptionLogger; } protected ConditionalExceptionLogger getExclusiveConsumerExceptionLogger() { return this.exclusiveConsumerExceptionLogger; } /** * Set to true to always requeue on transaction rollback with an external * {@link #setTransactionManager(PlatformTransactionManager) TransactionManager}. * With earlier releases, when a transaction manager was configured, a transaction * rollback always requeued the message. This was inconsistent with local transactions * where the normal {@link #setDefaultRequeueRejected(boolean) defaultRequeueRejected} * and {@link AmqpRejectAndDontRequeueException} logic was honored to determine whether * the message was requeued. RabbitMQ does not consider the message delivery to be part * of the transaction. * This boolean was introduced in 1.7.1, set to true by default, to be consistent with * previous behavior. Starting with version 2.0, it is false by default. * @param alwaysRequeueWithTxManagerRollback true to always requeue on rollback. * @since 1.7.1. */ public void setAlwaysRequeueWithTxManagerRollback(boolean alwaysRequeueWithTxManagerRollback) { this.alwaysRequeueWithTxManagerRollback = alwaysRequeueWithTxManagerRollback; } protected boolean isAlwaysRequeueWithTxManagerRollback() { return this.alwaysRequeueWithTxManagerRollback; } /** * Set the name (category) of the logger used to log exceptions thrown by the error handler. * It defaults to the container's logger but can be overridden if you want it to log at a different * level to the container. Such exceptions are logged at the ERROR level. * @param errorHandlerLoggerName the logger name. * @since 2.0.8 */ public void setErrorHandlerLoggerName(String errorHandlerLoggerName) { Assert.notNull(errorHandlerLoggerName, "'errorHandlerLoggerName' cannot be null"); this.errorHandlerLoggerName = errorHandlerLoggerName; } /** * Set a batching strategy to use when de-batching messages. * Default is {@link SimpleBatchingStrategy}. * @param batchingStrategy the strategy. * @since 2.2 * @see #setDeBatchingEnabled(boolean) */ public void setBatchingStrategy(BatchingStrategy batchingStrategy) { Assert.notNull(batchingStrategy, "'batchingStrategy' cannot be null"); this.batchingStrategy = batchingStrategy; } protected BatchingStrategy getBatchingStrategy() { return this.batchingStrategy; } protected Collection<MessagePostProcessor> getAfterReceivePostProcessors() { return this.afterReceivePostProcessors; } /** * Delegates to {@link #validateConfiguration()} and {@link #initialize()}. */ @Override public final void afterPropertiesSet() { super.afterPropertiesSet(); Assert.state(this.exposeListenerChannel || !getAcknowledgeMode().isManual(), "You cannot acknowledge messages manually if the channel is not exposed to the listener " + "(please check your configuration and set exposeListenerChannel=true or " + "acknowledgeMode!=MANUAL)"); Assert.state(!(getAcknowledgeMode().isAutoAck() && isChannelTransacted()), "The acknowledgeMode is NONE (autoack in Rabbit terms) which is not consistent with having a " + "transactional channel. Either use a different AcknowledgeMode or make sure " + "channelTransacted=false"); validateConfiguration(); initialize(); } @Override public void setupMessageListener(MessageListener messageListener) { setMessageListener(messageListener); } /** * Validate the configuration of this container. * <p> * The default implementation is empty. To be overridden in subclasses. */ protected void validateConfiguration() { } protected void initializeProxy(Object delegate) { if (getAdviceChain().length == 0) { return; } ProxyFactory factory = new ProxyFactory(); for (Advice advice : getAdviceChain()) { factory.addAdvisor(new DefaultPointcutAdvisor(advice)); } factory.addInterface(ContainerDelegate.class); factory.setTarget(delegate); this.proxy = (ContainerDelegate) factory.getProxy(ContainerDelegate.class.getClassLoader()); } /** * Calls {@link #shutdown()} when the BeanFactory destroys the container instance. * @see #shutdown() */ @Override public void destroy() { shutdown(); } // ------------------------------------------------------------------------- // Lifecycle methods for starting and stopping the container // ------------------------------------------------------------------------- /** * Initialize this container. * <p> * Creates a Rabbit Connection and calls {@link #doInitialize()}. */ public void initialize() { try { synchronized (this.lifecycleMonitor) { this.lifecycleMonitor.notifyAll(); } initializeProxy(this.delegate); checkMissingQueuesFatalFromProperty(); checkPossibleAuthenticationFailureFatalFromProperty(); doInitialize(); if (!this.isExposeListenerChannel() && this.transactionManager != null) { logger.warn("exposeListenerChannel=false is ignored when using a TransactionManager"); } if (!this.taskExecutorSet && StringUtils.hasText(getListenerId())) { this.taskExecutor = new SimpleAsyncTaskExecutor(getListenerId() + "-"); this.taskExecutorSet = true; } if (this.transactionManager != null && !isChannelTransacted()) { logger.debug("The 'channelTransacted' is coerced to 'true', when 'transactionManager' is provided"); setChannelTransacted(true); } if (this.messageListener != null) { this.messageListener.containerAckMode(this.acknowledgeMode); } this.initialized = true; } catch (Exception ex) { throw convertRabbitAccessException(ex); } } /** * Stop the shared Connection, call {@link #doShutdown()}, and close this container. */ public void shutdown() { synchronized (this.lifecycleMonitor) { if (!isActive()) { logger.info("Shutdown ignored - container is not active already"); return; } this.active = false; this.lifecycleMonitor.notifyAll(); } logger.debug("Shutting down Rabbit listener container"); // Shut down the invokers. try { doShutdown(); } catch (Exception ex) { throw convertRabbitAccessException(ex); } finally { synchronized (this.lifecycleMonitor) { this.running = false; this.lifecycleMonitor.notifyAll(); } } } /** * Register any invokers within this container. * <p> * Subclasses need to implement this method for their specific invoker management process. */ protected abstract void doInitialize(); /** * Close the registered invokers. * <p> * Subclasses need to implement this method for their specific invoker management process. * <p> * A shared Rabbit Connection, if any, will automatically be closed <i>afterwards</i>. * @see #shutdown() */ protected abstract void doShutdown(); /** * @return Whether this container is currently active, that is, whether it has been set up but not shut down yet. */ public final boolean isActive() { synchronized (this.lifecycleMonitor) { return this.active; } } /** * Start this container. * @see #doStart */ @Override public void start() { if (isRunning()) { return; } if (!this.initialized) { synchronized (this.lifecycleMonitor) { if (!this.initialized) { afterPropertiesSet(); } } } try { logger.debug("Starting Rabbit listener container."); configureAdminIfNeeded(); checkMismatchedQueues(); doStart(); } catch (Exception ex) { throw convertRabbitAccessException(ex); } finally { this.lazyLoad = false; } } /** * Start this container, and notify all invoker tasks. */ protected void doStart() { // Reschedule paused tasks, if any. synchronized (this.lifecycleMonitor) { this.active = true; this.running = true; this.lifecycleMonitor.notifyAll(); } } /** * Stop this container. * @see #doStop * @see #doStop */ @Override public void stop() { try { doStop(); } catch (Exception ex) { throw convertRabbitAccessException(ex); } finally { synchronized (this.lifecycleMonitor) { this.running = false; this.lifecycleMonitor.notifyAll(); } } } /** * This method is invoked when the container is stopping. */ protected void doStop() { shutdown(); } /** * Determine whether this container is currently running, that is, whether it has been started and not stopped yet. * @see #start() * @see #stop() */ @Override public final boolean isRunning() { synchronized (this.lifecycleMonitor) { return (this.running); } } /** * Invoke the registered ErrorHandler, if any. Log at error level otherwise. * The default error handler is a {@link ConditionalRejectingErrorHandler} with * the default {@link FatalExceptionStrategy} implementation. * @param ex the uncaught error that arose during Rabbit processing. * @see #setErrorHandler */ protected void invokeErrorHandler(Throwable ex) { if (this.errorHandler != null) { try { this.errorHandler.handleError(ex); } catch (Exception e) { LogFactory.getLog(this.errorHandlerLoggerName).error( "Execution of Rabbit message listener failed, and the error handler threw an exception", e); throw e; } } else { logger.warn("Execution of Rabbit message listener failed, and no ErrorHandler has been set.", ex); } } // ------------------------------------------------------------------------- // Template methods for listener execution // ------------------------------------------------------------------------- /** * Execute the specified listener, committing or rolling back the transaction afterwards (if necessary). * @param channel the Rabbit Channel to operate on * @param data the received Rabbit Message * @see #invokeListener * @see #handleListenerException */ @SuppressWarnings(UNCHECKED) protected void executeListener(Channel channel, Object data) { if (!isRunning()) { if (logger.isWarnEnabled()) { logger.warn( "Rejecting received message(s) because the listener container has been stopped: " + data); } throw new MessageRejectedWhileStoppingException(); } try { doExecuteListener(channel, data); } catch (RuntimeException ex) { Message message; if (data instanceof Message) { message = (Message) data; } else { message = ((List<Message>) data).get(0); } if (message.getMessageProperties().isFinalRetryForMessageWithNoId()) { if (this.statefulRetryFatalWithNullMessageId) { throw new FatalListenerExecutionException( "Illegal null id in message. Failed to manage retry for message: " + message, ex); } else { throw new ListenerExecutionFailedException("Cannot retry message more than once without an ID", new AmqpRejectAndDontRequeueException("Not retryable; rejecting and not requeuing", ex), message); } } handleListenerException(ex); throw ex; } } private void doExecuteListener(Channel channel, Object data) { if (data instanceof Message) { Message message = (Message) data; if (this.afterReceivePostProcessors != null) { for (MessagePostProcessor processor : this.afterReceivePostProcessors) { message = processor.postProcessMessage(message); if (message == null) { throw new ImmediateAcknowledgeAmqpException( "Message Post Processor returned 'null', discarding message"); } } } if (this.deBatchingEnabled && this.batchingStrategy.canDebatch(message.getMessageProperties())) { this.batchingStrategy.deBatch(message, fragment -> invokeListener(channel, fragment)); } else { invokeListener(channel, message); } } else { invokeListener(channel, data); } } protected void invokeListener(Channel channel, Object data) { this.proxy.invokeListener(channel, data); } /** * Invoke the specified listener: either as standard MessageListener or (preferably) as SessionAwareMessageListener. * @param channel the Rabbit Channel to operate on * @param data the received Rabbit Message or List of Message. * @see #setMessageListener(MessageListener) */ protected void actualInvokeListener(Channel channel, Object data) { Object listener = getMessageListener(); if (listener instanceof ChannelAwareMessageListener) { doInvokeListener((ChannelAwareMessageListener) listener, channel, data); } else if (listener instanceof MessageListener) { boolean bindChannel = isExposeListenerChannel() && isChannelLocallyTransacted(); if (bindChannel) { RabbitResourceHolder resourceHolder = new RabbitResourceHolder(channel, false); resourceHolder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager.bindResource(this.getConnectionFactory(), resourceHolder); } try { doInvokeListener((MessageListener) listener, data); } finally { if (bindChannel) { // unbind if we bound TransactionSynchronizationManager.unbindResource(this.getConnectionFactory()); } } } else if (listener != null) { throw new FatalListenerExecutionException( "Only MessageListener and SessionAwareMessageListener supported: " + listener); } else { throw new FatalListenerExecutionException( "No message listener specified - see property 'messageListener'"); } } /** * Invoke the specified listener as Spring ChannelAwareMessageListener, exposing a new Rabbit Session (potentially * with its own transaction) to the listener if demanded. * An exception thrown from the listener will be wrapped in a {@link ListenerExecutionFailedException}. * @param listener the Spring ChannelAwareMessageListener to invoke * @param channel the Rabbit Channel to operate on * @param data the received Rabbit Message or List of Message. * @see ChannelAwareMessageListener * @see #setExposeListenerChannel(boolean) */ @SuppressWarnings(UNCHECKED) protected void doInvokeListener(ChannelAwareMessageListener listener, Channel channel, Object data) { Message message = null; RabbitResourceHolder resourceHolder = null; Channel channelToUse = channel; boolean boundHere = false; try { if (!isExposeListenerChannel()) { // We need to expose a separate Channel. resourceHolder = getTransactionalResourceHolder(); channelToUse = resourceHolder.getChannel(); /* * If there is a real transaction, the resource will have been bound; otherwise * we need to bind it temporarily here. Any work done on this channel * will be committed in the finally block. */ if (isChannelLocallyTransacted() && !TransactionSynchronizationManager.isActualTransactionActive()) { resourceHolder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager.bindResource(this.getConnectionFactory(), resourceHolder); boundHere = true; } } else { // if locally transacted, bind the current channel to make it available to RabbitTemplate if (isChannelLocallyTransacted()) { RabbitResourceHolder localResourceHolder = new RabbitResourceHolder(channelToUse, false); localResourceHolder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager.bindResource(this.getConnectionFactory(), localResourceHolder); boundHere = true; } } // Actually invoke the message listener... try { if (data instanceof List) { listener.onMessageBatch((List<Message>) data, channelToUse); } else { message = (Message) data; listener.onMessage(message, channelToUse); } } catch (Exception e) { throw wrapToListenerExecutionFailedExceptionIfNeeded(e, data); } } finally { cleanUpAfterInvoke(resourceHolder, channelToUse, boundHere); } } private void cleanUpAfterInvoke(@Nullable RabbitResourceHolder resourceHolder, Channel channelToUse, boolean boundHere) { if (resourceHolder != null && boundHere) { // so the channel exposed (because exposeListenerChannel is false) will be closed resourceHolder.setSynchronizedWithTransaction(false); } ConnectionFactoryUtils.releaseResources(resourceHolder); // NOSONAR - null check in method if (boundHere) { // unbind if we bound TransactionSynchronizationManager.unbindResource(this.getConnectionFactory()); if (!isExposeListenerChannel() && isChannelLocallyTransacted()) { /* * commit the temporary channel we exposed; the consumer's channel * will be committed later. Note that when exposing a different channel * when there's no transaction manager, the exposed channel is committed * on each message, and not based on txSize. */ RabbitUtils.commitIfNecessary(channelToUse); } } } /** * Invoke the specified listener as Spring Rabbit MessageListener. * <p> * Default implementation performs a plain invocation of the <code>onMessage</code> method. * <p> * Exception thrown from listener will be wrapped to {@link ListenerExecutionFailedException}. * * @param listener the Rabbit MessageListener to invoke * @param data the received Rabbit Message or List of Message. * * @see org.springframework.amqp.core.MessageListener#onMessage */ @SuppressWarnings(UNCHECKED) protected void doInvokeListener(MessageListener listener, Object data) { Message message = null; try { if (data instanceof List) { listener.onMessageBatch((List<Message>) data); } else { message = (Message) data; listener.onMessage(message); } } catch (Exception e) { throw wrapToListenerExecutionFailedExceptionIfNeeded(e, data); } } /** * Check whether the given Channel is locally transacted, that is, whether its transaction is managed by this * listener container's Channel handling and not by an external transaction coordinator. * <p> * Note:This method is about finding out whether the Channel's transaction is local or externally coordinated. * @return whether the given Channel is locally transacted * @see #isChannelTransacted() */ protected boolean isChannelLocallyTransacted() { return this.isChannelTransacted() && this.transactionManager == null; } /** * Handle the given exception that arose during listener execution. * <p> * The default implementation logs the exception at error level, not propagating it to the Rabbit provider - * assuming that all handling of acknowledgment and/or transactions is done by this listener container. This can be * overridden in subclasses. * @param ex the exception to handle */ protected void handleListenerException(Throwable ex) { if (isActive()) { // Regular case: failed while active. // Invoke ErrorHandler if available. invokeErrorHandler(ex); } else { // Rare case: listener thread failed after container shutdown. // Log at debug level, to avoid spamming the shutdown log. logger.debug("Listener exception after container shutdown", ex); } } /** * @param e The Exception. * @param data The failed message. * @return If 'e' is of type {@link ListenerExecutionFailedException} - return 'e' as it is, otherwise wrap it to * {@link ListenerExecutionFailedException} and return. */ @SuppressWarnings(UNCHECKED) protected ListenerExecutionFailedException wrapToListenerExecutionFailedExceptionIfNeeded(Exception e, Object data) { if (!(e instanceof ListenerExecutionFailedException)) { // Wrap exception to ListenerExecutionFailedException. if (data instanceof List) { return new ListenerExecutionFailedException("Listener threw exception", e, ((List<Message>) data).toArray(new Message[0])); } else { return new ListenerExecutionFailedException("Listener threw exception", e, (Message) data); } } return (ListenerExecutionFailedException) e; } protected void publishConsumerFailedEvent(String reason, boolean fatal, @Nullable Throwable t) { if (this.applicationEventPublisher != null) { this.applicationEventPublisher .publishEvent(t == null ? new ListenerContainerConsumerTerminatedEvent(this, reason) : new ListenerContainerConsumerFailedEvent(this, reason, t, fatal)); } } protected final void publishIdleContainerEvent(long idleTime) { if (this.applicationEventPublisher != null) { this.applicationEventPublisher .publishEvent(new ListenerContainerIdleEvent(this, idleTime, getListenerId(), getQueueNames())); } } protected void updateLastReceive() { if (this.idleEventInterval > 0) { this.lastReceive = System.currentTimeMillis(); } } protected void configureAdminIfNeeded() { if (this.amqpAdmin == null && this.getApplicationContext() != null) { Map<String, AmqpAdmin> admins = this.getApplicationContext().getBeansOfType(AmqpAdmin.class); if (admins.size() == 1) { this.amqpAdmin = admins.values().iterator().next(); } else { if ((isAutoDeclare() || isMismatchedQueuesFatal()) && this.logger.isDebugEnabled()) { logger.debug("For 'autoDeclare' and 'mismatchedQueuesFatal' to work, there must be exactly one " + "AmqpAdmin in the context or you must inject one into this container; found: " + admins.size() + " for container " + toString()); } if (isMismatchedQueuesFatal()) { throw new IllegalStateException("When 'mismatchedQueuesFatal' is 'true', there must be exactly " + "one AmqpAdmin in the context or you must inject one into this container; found: " + admins.size() + " for container " + toString()); } } } } protected void checkMismatchedQueues() { if (this.mismatchedQueuesFatal && this.amqpAdmin != null) { try { this.amqpAdmin.initialize(); } catch (AmqpConnectException e) { logger.info("Broker not available; cannot check queue declarations"); } catch (AmqpIOException e) { if (RabbitUtils.isMismatchedQueueArgs(e)) { throw new FatalListenerStartupException("Mismatched queues", e); } else { logger.info("Failed to get connection during start(): " + e); } } } else { try { Connection connection = getConnectionFactory().createConnection(); // NOSONAR if (connection != null) { connection.close(); } } catch (Exception e) { logger.info( "Broker not available; cannot force queue declarations during start: " + e.getMessage()); } } } @Override public void lazyLoad() { if (this.mismatchedQueuesFatal) { if (this.missingQueuesFatal) { logger.warn( "'mismatchedQueuesFatal' and 'missingQueuesFatal' are ignored during the initial start(), " + "for lazily loaded containers"); } else { logger.warn("'mismatchedQueuesFatal' is ignored during the initial start(), " + "for lazily loaded containers"); } } else if (this.missingQueuesFatal) { logger.warn("'missingQueuesFatal' is ignored during the initial start(), " + "for lazily loaded containers"); } this.lazyLoad = true; } /** * Use {@link AmqpAdmin#initialize()} to redeclare everything if necessary. * Since auto deletion of a queue can cause upstream elements * (bindings, exchanges) to be deleted too, everything needs to be redeclared if * a queue is missing. * Declaration is idempotent so, aside from some network chatter, there is no issue, * and we only will do it if we detect our queue is gone. * <p> * In general it makes sense only for the 'auto-delete' or 'expired' queues, * but with the server TTL policy we don't have ability to determine 'expiration' * option for the queue. * <p> * Starting with version 1.6, if * {@link #setMismatchedQueuesFatal(boolean) mismatchedQueuesFatal} is true, * the declarations are always attempted during restart so the listener will * fail with a fatal error if mismatches occur. */ protected synchronized void redeclareElementsIfNecessary() { AmqpAdmin admin = getAmqpAdmin(); if (!this.lazyLoad && admin != null && isAutoDeclare()) { try { attemptDeclarations(admin); } catch (Exception e) { if (RabbitUtils.isMismatchedQueueArgs(e)) { throw new FatalListenerStartupException("Mismatched queues", e); } logger.error("Failed to check/redeclare auto-delete queue(s).", e); } } } private void attemptDeclarations(AmqpAdmin admin) { ApplicationContext context = this.getApplicationContext(); if (context != null) { Set<String> queueNames = getQueueNamesAsSet(); Map<String, Queue> queueBeans = context.getBeansOfType(Queue.class); for (Entry<String, Queue> entry : queueBeans.entrySet()) { Queue queue = entry.getValue(); if (isMismatchedQueuesFatal() || (queueNames.contains(queue.getName()) && admin.getQueueProperties(queue.getName()) == null)) { if (logger.isDebugEnabled()) { logger.debug("Redeclaring context exchanges, queues, bindings."); } admin.initialize(); break; } } } } /** * Traverse the cause chain and, if an {@link ImmediateAcknowledgeAmqpException} * is found before an {@link AmqpRejectAndDontRequeueException}, return true. * An {@link Error} will take precedence. * @param ex the exception * @return true if we should ack immediately. * @since 1.6.6 */ protected boolean causeChainHasImmediateAcknowledgeAmqpException(Throwable ex) { if (ex instanceof Error) { return false; } Throwable cause = ex.getCause(); while (cause != null) { if (cause instanceof ImmediateAcknowledgeAmqpException) { return true; } else if (cause instanceof AmqpRejectAndDontRequeueException || cause instanceof Error) { return false; } cause = cause.getCause(); } return false; } /** * A null resource holder is rare, but possible if the transaction attribute caused no * transaction to be started (e.g. {@code TransactionDefinition.PROPAGATION_NONE}). In * that case the delivery tags will have been processed manually. * @param resourceHolder the bound resource holder (if a transaction is active). * @param exception the exception. */ protected void prepareHolderForRollback(RabbitResourceHolder resourceHolder, RuntimeException exception) { if (resourceHolder != null) { resourceHolder.setRequeueOnRollback(isAlwaysRequeueWithTxManagerRollback() || ContainerUtils.shouldRequeue(isDefaultRequeueRejected(), exception, logger)); } } private void checkMissingQueuesFatalFromProperty() { if (!isMissingQueuesFatalSet()) { try { ApplicationContext context = getApplicationContext(); if (context != null) { Properties properties = context.getBean("spring.amqp.global.properties", Properties.class); String missingQueuesFatalProperty = properties.getProperty("mlc.missing.queues.fatal"); if (!StringUtils.hasText(missingQueuesFatalProperty)) { missingQueuesFatalProperty = properties.getProperty("smlc.missing.queues.fatal"); } if (StringUtils.hasText(missingQueuesFatalProperty)) { setMissingQueuesFatal(Boolean.parseBoolean(missingQueuesFatalProperty)); } } } catch (BeansException be) { logger.debug("No global properties bean"); } } } private void checkPossibleAuthenticationFailureFatalFromProperty() { if (!isPossibleAuthenticationFailureFatal()) { try { ApplicationContext context = getApplicationContext(); if (context != null) { Properties properties = context.getBean("spring.amqp.global.properties", Properties.class); String possibleAuthenticationFailureFatalProperty = properties .getProperty("mlc.possible.authentication.failure.fatal"); if (StringUtils.hasText(possibleAuthenticationFailureFatalProperty)) { setPossibleAuthenticationFailureFatal( Boolean.parseBoolean(possibleAuthenticationFailureFatalProperty)); } } } catch (BeansException be) { logger.debug("No global properties bean"); } } } @FunctionalInterface private interface ContainerDelegate { void invokeListener(Channel channel, Object data); } /** * Exception that indicates that the initial setup of this container's shared Rabbit Connection failed. This is * indicating to invokers that they need to establish the shared Connection themselves on first access. */ @SuppressWarnings("serial") public static class SharedConnectionNotInitializedException extends RuntimeException { /** * Create a new SharedConnectionNotInitializedException. * @param msg the detail message */ protected SharedConnectionNotInitializedException(String msg) { super(msg); } } /** * A runtime exception to wrap a {@link Throwable}. */ @SuppressWarnings("serial") protected static final class WrappedTransactionException extends RuntimeException { protected WrappedTransactionException(Throwable cause) { super(cause); } } /** * Default implementation of {@link ConditionalExceptionLogger} for logging exclusive * consumer failures. * @since 1.5 */ private static class DefaultExclusiveConsumerLogger implements ConditionalExceptionLogger { DefaultExclusiveConsumerLogger() { super(); } @Override public void log(Log logger, String message, Throwable t) { if (t instanceof ShutdownSignalException) { ShutdownSignalException cause = (ShutdownSignalException) t; if (RabbitUtils.isExclusiveUseChannelClose(cause)) { if (logger.isWarnEnabled()) { logger.warn(message + ": " + cause.toString()); } } else if (!RabbitUtils.isNormalChannelClose(cause)) { logger.error(message + ": " + cause.getMessage()); } } else { if (logger.isErrorEnabled()) { logger.error("Unexpected invocation of " + getClass() + ", with message: " + message, t); } } } } }