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

io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer 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.listener;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

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.ImmediateAcknowledgeAmqpException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.AcknowledgeMode;
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.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.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.connection.RoutingConnectionFactory;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.exception.FatalListenerExecutionException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.converter.MessageConversionException;
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.postprocessor.MessagePostProcessorUtils;
import io.bitsensor.plugins.shaded.org.springframework.beans.factory.BeanNameAware;
import io.bitsensor.plugins.shaded.org.springframework.beans.factory.DisposableBean;
import io.bitsensor.plugins.shaded.org.springframework.context.ApplicationContext;
import io.bitsensor.plugins.shaded.org.springframework.context.ApplicationContextAware;
import io.bitsensor.plugins.shaded.org.springframework.context.SmartLifecycle;
import io.bitsensor.plugins.shaded.org.springframework.transaction.support.TransactionSynchronizationManager;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.org.springframework.util.ErrorHandler;

import io.bitsensor.plugins.shaded.com.rabbitmq.client.Channel;

/**
 * @author Mark Pollack
 * @author Mark Fisher
 * @author Dave Syer
 * @author James Carr
 * @author Gary Russell
 */
public abstract class AbstractMessageListenerContainer extends RabbitAccessor
		implements MessageListenerContainer, ApplicationContextAware, BeanNameAware, DisposableBean, SmartLifecycle {

	public static final boolean DEFAULT_DEBATCHING_ENABLED = true;

	private volatile String beanName;

	private volatile 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 queueNames = new CopyOnWriteArrayList();

	private ErrorHandler errorHandler = new ConditionalRejectingErrorHandler();

	private MessageConverter messageConverter;

	private boolean exposeListenerChannel = true;

	private volatile Object messageListener;

	private volatile AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;

	private volatile boolean deBatchingEnabled = DEFAULT_DEBATCHING_ENABLED;

	private volatile boolean initialized;

	private Collection afterReceivePostProcessors;

	private volatile ApplicationContext applicationContext;

	private String listenerId;

	private String lookupKeyQualifier = "";

	/**
	 * 

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

*

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

*

* 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 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 null) */ public void setQueueNames(String... queueName) { Assert.noNullElements(queueName, "Queue name(s) cannot be null"); this.queueNames = new CopyOnWriteArrayList(Arrays.asList(queueName)); } /** * Set the name of the queue(s) to receive messages from. * @param queues the desired queue(s) (can not be null) */ public void setQueues(Queue... queues) { List queueNames = new ArrayList(queues.length); for (int i = 0; i < queues.length; i++) { Assert.notNull(queues[i], "Queue (" + i + ") must not be null."); queueNames.add(queues[i].getName()); } queueNames = new CopyOnWriteArrayList(queueNames); this.queueNames = queueNames; } /** * @return the name of the queues to receive messages from. */ public String[] getQueueNames() { return this.queueNames.toArray(new String[this.queueNames.size()]); } protected String[] getRequiredQueueNames() { Assert.state(this.queueNames.size() > 0, "Queue names must not be empty."); return getQueueNames(); } protected Set getQueueNamesAsSet() { return new HashSet(this.queueNames); } /** * 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"); this.queueNames.addAll(Arrays.asList(queueNames)); } /** * 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"); String[] queueNames = new String[queues.length]; for (int i = 0; i < queues.length; i++) { queueNames[i] = queues[i].getName(); } this.addQueueNames(queueNames); } /** * 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"); return this.queueNames.removeAll(Arrays.asList(queueNames)); } /** * 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"); String[] queueNames = new String[queues.length]; for (int i = 0; i < queues.length; i++) { queueNames[i] = queues[i].getName(); } return this.removeQueueNames(queueNames); } /** * @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 io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core.RabbitTemplate} calls. *

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

* Note that Channels managed by an external transaction manager will always get exposed to * {@link io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.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 message listener implementation to register. This can be either a Spring {@link MessageListener} object * or a Spring {@link ChannelAwareMessageListener} object. * * @param messageListener The listener. * @throws IllegalArgumentException if the supplied listener is not a {@link MessageListener} or a * {@link ChannelAwareMessageListener} * @see MessageListener * @see ChannelAwareMessageListener */ public void setMessageListener(Object messageListener) { checkMessageListener(messageListener); this.messageListener = messageListener; } /** * Check the given message listener, throwing an exception if it does not correspond to a supported listener type. *

* By default, only a Spring {@link MessageListener} object or a Spring * {@link ChannelAwareMessageListener} object will be accepted. * @param messageListener the message listener object to check * @throws IllegalArgumentException if the supplied listener is not a MessageListener or SessionAwareMessageListener * @see MessageListener * @see ChannelAwareMessageListener */ protected void checkMessageListener(Object messageListener) { if (!(messageListener instanceof MessageListener || messageListener instanceof ChannelAwareMessageListener)) { 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 no ErrorHandler so that error-level logging is the only result. * * @param errorHandler The error handler. */ public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } /** * Set the {@link MessageConverter} strategy for converting AMQP Messages. * @param messageConverter the message converter to use */ public void setMessageConverter(MessageConverter messageConverter) { this.messageConverter = messageConverter; } @Override public MessageConverter getMessageConverter() { return this.messageConverter; } /** * 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. */ public void setDeBatchingEnabled(boolean deBatchingEnabled) { this.deBatchingEnabled = deBatchingEnabled; } /** * 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 */ 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 whether to automatically start the container after initialization. *

* 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. */ 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()); 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; } /** * 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) */ protected String getRoutingLookupKey() { return super.getConnectionFactory() instanceof RoutingConnectionFactory ? (this.lookupKeyQualifier + this.queueNames.toString().replaceAll(" ", "")) : 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 */ 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). */ public String getListenerId() { return this.listenerId != null ? this.listenerId : this.beanName; } public void setListenerId(String listenerId) { this.listenerId = listenerId; } /** * 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(Object messageListener) { setMessageListener(messageListener); } /** * Validate the configuration of this container. *

* The default implementation is empty. To be overridden in subclasses. */ protected void validateConfiguration() { } /** * 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. *

* Creates a Rabbit Connection and calls {@link #doInitialize()}. */ public void initialize() { try { synchronized (this.lifecycleMonitor) { this.lifecycleMonitor.notifyAll(); } doInitialize(); } catch (Exception ex) { throw convertRabbitAccessException(ex); } } /** * Stop the shared Connection, call {@link #doShutdown()}, and close this container. */ public void shutdown() { logger.debug("Shutting down Rabbit listener container"); synchronized (this.lifecycleMonitor) { this.active = false; this.lifecycleMonitor.notifyAll(); } // 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. *

* Subclasses need to implement this method for their specific invoker management process. * * @throws Exception Any Exception. */ protected abstract void doInitialize() throws Exception; /** * Close the registered invokers. *

* Subclasses need to implement this method for their specific invoker management process. *

* A shared Rabbit Connection, if any, will automatically be closed afterwards. * @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 (!this.initialized) { synchronized (this.lifecycleMonitor) { if (!this.initialized) { afterPropertiesSet(); this.initialized = true; } } } try { if (logger.isDebugEnabled()) { logger.debug("Starting Rabbit listener container."); } doStart(); } catch (Exception ex) { throw convertRabbitAccessException(ex); } } /** * Start this container, and notify all invoker tasks. * @throws Exception if thrown by Rabbit API methods */ protected void doStart() throws Exception { // 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(); } } } @Override public void stop(Runnable callback) { try { stop(); } finally { callback.run(); } } /** * This method is invoked when the container is stopping. The default implementation does nothing, but subclasses * may override. */ protected void doStop() { } /** * 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) { this.errorHandler.handleError(ex); } else if (logger.isWarnEnabled()) { 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 messageIn the received Rabbit Message * @throws Throwable Any Throwable. * * @see #invokeListener * @see #handleListenerException */ protected void executeListener(Channel channel, Message messageIn) throws Throwable { if (!isRunning()) { if (logger.isWarnEnabled()) { logger.warn("Rejecting received message because the listener container has been stopped: " + messageIn); } throw new MessageRejectedWhileStoppingException(); } try { Message message = messageIn; if (this.afterReceivePostProcessors != null) { for (MessagePostProcessor processor : this.afterReceivePostProcessors) { message = processor.postProcessMessage(message); } } Object batchFormat = message.getMessageProperties().getHeaders().get(MessageProperties.SPRING_BATCH_FORMAT); if (MessageProperties.BATCH_FORMAT_LENGTH_HEADER4.equals(batchFormat) && this.deBatchingEnabled) { ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBody()); MessageProperties messageProperties = message.getMessageProperties(); messageProperties.getHeaders().remove(MessageProperties.SPRING_BATCH_FORMAT); while (byteBuffer.hasRemaining()) { int length = byteBuffer.getInt(); if (length < 0 || length > byteBuffer.remaining()) { throw new ListenerExecutionFailedException("Bad batched message received", new MessageConversionException("Insufficient batch data at offset " + byteBuffer.position()), message); } byte[] body = new byte[length]; byteBuffer.get(body); messageProperties.setContentLength(length); // Caveat - shared MessageProperties. Message fragment = new Message(body, messageProperties); invokeListener(channel, fragment); } } else { invokeListener(channel, message); } } catch (Exception ex) { handleListenerException(ex); throw ex; } } /** * Invoke the specified listener: either as standard MessageListener or (preferably) as SessionAwareMessageListener. * @param channel the Rabbit Channel to operate on * @param message the received Rabbit Message * @throws Exception if thrown by Rabbit API methods * @see #setMessageListener */ protected void invokeListener(Channel channel, Message message) throws Exception { Object listener = getMessageListener(); if (listener instanceof ChannelAwareMessageListener) { doInvokeListener((ChannelAwareMessageListener) listener, channel, message); } else if (listener instanceof MessageListener) { boolean bindChannel = isExposeListenerChannel() && isChannelLocallyTransacted(channel); if (bindChannel) { RabbitResourceHolder resourceHolder = new RabbitResourceHolder(channel, false); resourceHolder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager.bindResource(this.getConnectionFactory(), resourceHolder); } try { doInvokeListener((MessageListener) listener, message); } 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. * @param listener the Spring ChannelAwareMessageListener to invoke * @param channel the Rabbit Channel to operate on * @param message the received Rabbit Message * @throws Exception if thrown by Rabbit API methods or listener itself. *

* Exception thrown from listener will be wrapped to {@link ListenerExecutionFailedException}. * @see ChannelAwareMessageListener * @see #setExposeListenerChannel(boolean) */ protected void doInvokeListener(ChannelAwareMessageListener listener, Channel channel, Message message) throws Exception { 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(channelToUse) && !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(channel)) { RabbitResourceHolder localResourceHolder = new RabbitResourceHolder(channelToUse, false); localResourceHolder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager.bindResource(this.getConnectionFactory(), localResourceHolder); boundHere = true; } } // Actually invoke the message listener... try { listener.onMessage(message, channelToUse); } catch (Exception e) { throw wrapToListenerExecutionFailedExceptionIfNeeded(e, message); } } finally { if (resourceHolder != null && boundHere) { // so the channel exposed (because exposeListenerChannel is false) will be closed resourceHolder.setSynchronizedWithTransaction(false); } ConnectionFactoryUtils.releaseResources(resourceHolder); if (boundHere) { // unbind if we bound TransactionSynchronizationManager.unbindResource(this.getConnectionFactory()); if (!isExposeListenerChannel() && isChannelLocallyTransacted(channelToUse)) { /* * 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. *

* Default implementation performs a plain invocation of the onMessage method. *

* Exception thrown from listener will be wrapped to {@link ListenerExecutionFailedException}. * * @param listener the Rabbit MessageListener to invoke * @param message the received Rabbit Message * @throws Exception Any Exception. * * @see io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessageListener#onMessage */ protected void doInvokeListener(MessageListener listener, Message message) throws Exception { try { listener.onMessage(message); } catch (Exception e) { throw wrapToListenerExecutionFailedExceptionIfNeeded(e, message); } } /** * 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. *

* Note:This method is about finding out whether the Channel's transaction is local or externally coordinated. * @param channel the Channel to check * @return whether the given Channel is locally transacted * @see #isChannelTransacted() */ protected boolean isChannelLocallyTransacted(Channel channel) { return isChannelTransacted(); } /** * Handle the given exception that arose during listener execution. *

* 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 message The failed message. * @return If 'e' is of type {@link ListenerExecutionFailedException} - return 'e' as it is, otherwise wrap it to * {@link ListenerExecutionFailedException} and return. */ protected Exception wrapToListenerExecutionFailedExceptionIfNeeded(Exception e, Message message) { if (!(e instanceof ListenerExecutionFailedException)) { // Wrap exception to ListenerExecutionFailedException. return new ListenerExecutionFailedException("Listener threw exception", e, message); } return e; } /** * 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; } /** * 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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy