Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » jms » listener » [javadoc | source]
    1   /*
    2    * Copyright 2002-2008 the original author or authors.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package org.springframework.jms.listener;
   18   
   19   import javax.jms.Connection;
   20   import javax.jms.Destination;
   21   import javax.jms.ExceptionListener;
   22   import javax.jms.JMSException;
   23   import javax.jms.Message;
   24   import javax.jms.MessageListener;
   25   import javax.jms.Queue;
   26   import javax.jms.Session;
   27   import javax.jms.Topic;
   28   
   29   import org.springframework.jms.support.JmsUtils;
   30   import org.springframework.util.Assert;
   31   
   32   /**
   33    * Abstract base class for message listener containers. Can either host
   34    * a standard JMS {@link javax.jms.MessageListener} or a Spring-specific
   35    * {@link SessionAwareMessageListener}.
   36    *
   37    * <p>Usually holds a single JMS {@link Connection} that all listeners are
   38    * supposed to be registered on, which is the standard JMS way of managing
   39    * listeners. Can alternatively also be used with a fresh Connection per
   40    * listener, for J2EE-style XA-aware JMS messaging. The actual registration
   41    * process is up to concrete subclasses.
   42    *
   43    * <p><b>NOTE:</b> The default behavior of this message listener container
   44    * is to <b>never</b> propagate an exception thrown by a message listener up to
   45    * the JMS provider. Instead, it will log any such exception at the error level.
   46    * This means that from the perspective of the attendant JMS provider no such
   47    * listener will ever fail.
   48    *
   49    * <p>The listener container offers the following message acknowledgment options:
   50    * <ul>
   51    * <li>"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
   52    * Automatic message acknowledgment <i>before</i> listener execution;
   53    * no redelivery in case of exception thrown.
   54    * <li>"sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE":
   55    * Automatic message acknowledgment <i>after</i> successful listener execution;
   56    * no redelivery in case of exception thrown.
   57    * <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE":
   58    * <i>Lazy</i> message acknowledgment during or after listener execution;
   59    * <i>potential redelivery</i> in case of exception thrown.
   60    * <li>"sessionTransacted" set to "true":
   61    * Transactional acknowledgment after successful listener execution;
   62    * <i>guaranteed redelivery</i> in case of exception thrown.
   63    * </ul>
   64    * The exact behavior might vary according to the concrete listener container
   65    * and JMS provider used.
   66    *
   67    * <p>There are two solutions to the duplicate processing problem:
   68    * <ul>
   69    * <li>Either add <i>duplicate message detection</i> to your listener, in the
   70    * form of a business entity existence check or a protocol table check. This
   71    * usually just needs to be done in case of the JMSRedelivered flag being
   72    * set on the incoming message (else just process straightforwardly).
   73    * <li>Or wrap the <i>entire processing with an XA transaction</i>, covering the
   74    * reception of the message as well as the execution of the message listener.
   75    * This is only supported by {@link DefaultMessageListenerContainer}, through
   76    * specifying a "transactionManager" (typically a
   77    * {@link org.springframework.transaction.jta.JtaTransactionManager}, with
   78    * a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in as
   79    * "connectionFactory").
   80    * </ul>
   81    * Note that XA transaction coordination adds significant runtime overhead,
   82    * so it might be feasible to avoid it unless absolutely necessary.
   83    *
   84    * <p><b>Recommendations:</b>
   85    * <ul>
   86    * <li>The general recommendation is to set "sessionTransacted" to "true",
   87    * typically in combination with local database transactions triggered by the
   88    * listener implementation, through Spring's standard transaction facilities.
   89    * This will work nicely in Tomcat or in a standalone environment, often
   90    * combined with custom duplicate message detection (if it is unacceptable
   91    * to ever process the same message twice).
   92    * <li>Alternatively, specify a
   93    * {@link org.springframework.transaction.jta.JtaTransactionManager} as
   94    * "transactionManager" for a fully XA-aware JMS provider - typically when
   95    * running on a J2EE server, but also for other environments with a JTA
   96    * transaction manager present. This will give full "exactly-once" guarantees
   97    * without custom duplicate message checks, at the price of additional
   98    * runtime processing overhead.
   99    * </ul>
  100    *
  101    * <p>Note that it is also possible to specify a
  102    * {@link org.springframework.jms.connection.JmsTransactionManager} as external
  103    * "transactionManager", providing fully synchronized Spring transactions based
  104    * on local JMS transactions. The effect is similar to "sessionTransacted" set
  105    * to "true", the difference being that this external transaction management
  106    * will also affect independent JMS access code within the service layer
  107    * (e.g. based on {@link org.springframework.jms.core.JmsTemplate} or
  108    * {@link org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy}),
  109    * not just direct JMS Session usage in a {@link SessionAwareMessageListener}.
  110    *
  111    * @author Juergen Hoeller
  112    * @since 2.0
  113    * @see #setMessageListener
  114    * @see javax.jms.MessageListener
  115    * @see SessionAwareMessageListener
  116    * @see #handleListenerException
  117    * @see DefaultMessageListenerContainer
  118    * @see SimpleMessageListenerContainer
  119    * @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
  120    */
  121   public abstract class AbstractMessageListenerContainer extends AbstractJmsListeningContainer {
  122   
  123   	private volatile Object destination;
  124   
  125   	private volatile String messageSelector;
  126   
  127   	private volatile Object messageListener;
  128   
  129   	private boolean subscriptionDurable = false;
  130   
  131   	private String durableSubscriptionName;
  132   
  133   	private ExceptionListener exceptionListener;
  134   
  135   	private boolean exposeListenerSession = true;
  136   
  137   	private boolean acceptMessagesWhileStopping = false;
  138   
  139   
  140   	/**
  141   	 * Set the destination to receive messages from.
  142   	 * <p>Alternatively, specify a "destinationName", to be dynamically
  143   	 * resolved via the {@link org.springframework.jms.support.destination.DestinationResolver}.
  144   	 * <p>Note: The destination may be replaced at runtime, with the listener
  145   	 * container picking up the new destination immediately (works e.g. with
  146   	 * DefaultMessageListenerContainer, as long as the cache level is less than
  147   	 * CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
  148   	 * @see #setDestinationName(String)
  149   	 */
  150   	public void setDestination(Destination destination) {
  151   		Assert.notNull(destination, "'destination' must not be null");
  152   		this.destination = destination;
  153   		if (destination instanceof Topic && !(destination instanceof Queue)) {
  154   			// Clearly a Topic: let's set the "pubSubDomain" flag accordingly.
  155   			setPubSubDomain(true);
  156   		}
  157   	}
  158   
  159   	/**
  160   	 * Return the destination to receive messages from. Will be <code>null</code>
  161   	 * if the configured destination is not an actual {@link Destination} type;
  162   	 * c.f. {@link #setDestinationName(String) when the destination is a String}.
  163   	 */
  164   	public Destination getDestination() {
  165   		return (this.destination instanceof Destination ? (Destination) this.destination : null);
  166   	}
  167   
  168   	/**
  169   	 * Set the name of the destination to receive messages from.
  170   	 * <p>The specified name will be dynamically resolved via the configured
  171   	 * {@link #setDestinationResolver destination resolver}.
  172   	 * <p>Alternatively, specify a JMS {@link Destination} object as "destination".
  173   	 * <p>Note: The destination may be replaced at runtime, with the listener
  174   	 * container picking up the new destination immediately (works e.g. with
  175   	 * DefaultMessageListenerContainer, as long as the cache level is less than
  176   	 * CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
  177   	 * @param destinationName the desired destination (can be <code>null</code>)
  178   	 * @see #setDestination(javax.jms.Destination)
  179   	 */
  180   	public void setDestinationName(String destinationName) {
  181   		Assert.notNull(destinationName, "'destinationName' must not be null");
  182   		this.destination = destinationName;
  183   	}
  184   
  185   	/**
  186   	 * Return the name of the destination to receive messages from.
  187   	 * Will be <code>null</code> if the configured destination is not a
  188   	 * {@link String} type; c.f. {@link #setDestination(Destination) when
  189   	 * it is an actual Destination}.
  190   	 */
  191   	public String getDestinationName() {
  192   		return (this.destination instanceof String ? (String) this.destination : null);
  193   	}
  194   
  195   	/**
  196   	 * Return a descriptive String for this container's JMS destination
  197   	 * (never <code>null</code>).
  198   	 */
  199   	protected String getDestinationDescription() {
  200   		return this.destination.toString();
  201   	}
  202   
  203   	/**
  204   	 * Set the JMS message selector expression (or <code>null</code> if none).
  205   	 * Default is none.
  206   	 * <p>See the JMS specification for a detailed definition of selector expressions.
  207   	 * <p>Note: The message selector may be replaced at runtime, with the listener
  208   	 * container picking up the new selector value immediately (works e.g. with
  209   	 * DefaultMessageListenerContainer, as long as the cache level is less than
  210   	 * CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
  211   	 */
  212   	public void setMessageSelector(String messageSelector) {
  213   		this.messageSelector = messageSelector;
  214   	}
  215   
  216   	/**
  217   	 * Return the JMS message selector expression (or <code>null</code> if none).
  218   	 */
  219   	public String getMessageSelector() {
  220   		return this.messageSelector;
  221   	}
  222   
  223   
  224   	/**
  225   	 * Set the message listener implementation to register.
  226   	 * This can be either a standard JMS {@link MessageListener} object
  227   	 * or a Spring {@link SessionAwareMessageListener} object.
  228   	 * <p>Note: The message listener may be replaced at runtime, with the listener
  229   	 * container picking up the new listener object immediately (works e.g. with
  230   	 * DefaultMessageListenerContainer, as long as the cache level is less than
  231   	 * CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
  232   	 * @throws IllegalArgumentException if the supplied listener is not a
  233   	 * {@link MessageListener} or a {@link SessionAwareMessageListener}
  234   	 * @see javax.jms.MessageListener
  235   	 * @see SessionAwareMessageListener
  236   	 */
  237   	public void setMessageListener(Object messageListener) {
  238   		checkMessageListener(messageListener);
  239   		this.messageListener = messageListener;
  240   		if (this.durableSubscriptionName == null) {
  241   			this.durableSubscriptionName = getDefaultSubscriptionName(messageListener);
  242   		}
  243   	}
  244   
  245   	/**
  246   	 * Return the message listener object to register.
  247   	 */
  248   	public Object getMessageListener() {
  249   		return this.messageListener;
  250   	}
  251   
  252   	/**
  253   	 * Check the given message listener, throwing an exception
  254   	 * if it does not correspond to a supported listener type.
  255   	 * <p>By default, only a standard JMS {@link MessageListener} object or a
  256   	 * Spring {@link SessionAwareMessageListener} object will be accepted.
  257   	 * @param messageListener the message listener object to check
  258   	 * @throws IllegalArgumentException if the supplied listener is not a
  259   	 * {@link MessageListener} or a {@link SessionAwareMessageListener}
  260   	 * @see javax.jms.MessageListener
  261   	 * @see SessionAwareMessageListener
  262   	 */
  263   	protected void checkMessageListener(Object messageListener) {
  264   		if (!(messageListener instanceof MessageListener ||
  265   				messageListener instanceof SessionAwareMessageListener)) {
  266   			throw new IllegalArgumentException(
  267   					"Message listener needs to be of type [" + MessageListener.class.getName() +
  268   					"] or [" + SessionAwareMessageListener.class.getName() + "]");
  269   		}
  270   	}
  271   
  272   	/**
  273   	 * Determine the default subscription name for the given message listener.
  274   	 * @param messageListener the message listener object to check
  275   	 * @return the default subscription name
  276   	 * @see SubscriptionNameProvider
  277   	 */
  278   	protected String getDefaultSubscriptionName(Object messageListener) {
  279   		if (messageListener instanceof SubscriptionNameProvider) {
  280   			return ((SubscriptionNameProvider) messageListener).getSubscriptionName();
  281   		}
  282   		else {
  283   			return messageListener.getClass().getName();
  284   		}
  285   	}
  286   
  287   	/**
  288   	 * Set whether to make the subscription durable. The durable subscription name
  289   	 * to be used can be specified through the "durableSubscriptionName" property.
  290   	 * <p>Default is "false". Set this to "true" to register a durable subscription,
  291   	 * typically in combination with a "durableSubscriptionName" value (unless
  292   	 * your message listener class name is good enough as subscription name).
  293   	 * <p>Only makes sense when listening to a topic (pub-sub domain).
  294   	 * @see #setDurableSubscriptionName
  295   	 */
  296   	public void setSubscriptionDurable(boolean subscriptionDurable) {
  297   		this.subscriptionDurable = subscriptionDurable;
  298   	}
  299   
  300   	/**
  301   	 * Return whether to make the subscription durable.
  302   	 */
  303   	public boolean isSubscriptionDurable() {
  304   		return this.subscriptionDurable;
  305   	}
  306   
  307   	/**
  308   	 * Set the name of a durable subscription to create. To be applied in case
  309   	 * of a topic (pub-sub domain) with subscription durability activated.
  310   	 * <p>The durable subscription name needs to be unique within this client's
  311   	 * JMS client id. Default is the class name of the specified message listener.
  312   	 * <p>Note: Only 1 concurrent consumer (which is the default of this
  313   	 * message listener container) is allowed for each durable subscription.
  314   	 * @see #setSubscriptionDurable
  315   	 * @see #setClientId
  316   	 * @see #setMessageListener
  317   	 */
  318   	public void setDurableSubscriptionName(String durableSubscriptionName) {
  319   		this.durableSubscriptionName = durableSubscriptionName;
  320   	}
  321   
  322   	/**
  323   	 * Return the name of a durable subscription to create, if any.
  324   	 */
  325   	public String getDurableSubscriptionName() {
  326   		return this.durableSubscriptionName;
  327   	}
  328   
  329   	/**
  330   	 * Set the JMS ExceptionListener to notify in case of a JMSException thrown
  331   	 * by the registered message listener or the invocation infrastructure.
  332   	 */
  333   	public void setExceptionListener(ExceptionListener exceptionListener) {
  334   		this.exceptionListener = exceptionListener;
  335   	}
  336   
  337   	/**
  338   	 * Return the JMS ExceptionListener to notify in case of a JMSException thrown
  339   	 * by the registered message listener or the invocation infrastructure, if any.
  340   	 */
  341   	public ExceptionListener getExceptionListener() {
  342   		return this.exceptionListener;
  343   	}
  344   
  345   	/**
  346   	 * Set whether to expose the listener JMS Session to a registered
  347   	 * {@link SessionAwareMessageListener} as well as to
  348   	 * {@link org.springframework.jms.core.JmsTemplate} calls.
  349   	 * <p>Default is "true", reusing the listener's {@link Session}.
  350   	 * Turn this off to expose a fresh JMS Session fetched from the same
  351   	 * underlying JMS {@link Connection} instead, which might be necessary
  352   	 * on some JMS providers.
  353   	 * <p>Note that Sessions managed by an external transaction manager will
  354   	 * always get exposed to {@link org.springframework.jms.core.JmsTemplate}
  355   	 * calls. So in terms of JmsTemplate exposure, this setting only affects
  356   	 * locally transacted Sessions.
  357   	 * @see SessionAwareMessageListener
  358   	 */
  359   	public void setExposeListenerSession(boolean exposeListenerSession) {
  360   		this.exposeListenerSession = exposeListenerSession;
  361   	}
  362   
  363   	/**
  364   	 * Return whether to expose the listener JMS {@link Session} to a
  365   	 * registered {@link SessionAwareMessageListener}.
  366   	 */
  367   	public boolean isExposeListenerSession() {
  368   		return this.exposeListenerSession;
  369   	}
  370   
  371   	/**
  372   	 * Set whether to accept received messages while the listener container
  373   	 * in the process of stopping.
  374   	 * <p>Default is "false", rejecting such messages through aborting the
  375   	 * receive attempt. Switch this flag on to fully process such messages
  376   	 * even in the stopping phase, with the drawback that even newly sent
  377   	 * messages might still get processed (if coming in before all receive
  378   	 * timeouts have expired).
  379   	 * <p><b>NOTE:</b> Aborting receive attempts for such incoming messages
  380   	 * might lead to the provider's retry count decreasing for the affected
  381   	 * messages. If you have a high number of concurrent consumers, make sure
  382   	 * that the number of retries is higher than the number of consumers,
  383   	 * to be on the safe side for all potential stopping scenarios.
  384   	 */
  385   	public void setAcceptMessagesWhileStopping(boolean acceptMessagesWhileStopping) {
  386   		this.acceptMessagesWhileStopping = acceptMessagesWhileStopping;
  387   	}
  388   
  389   	/**
  390   	 * Return whether to accept received messages while the listener container
  391   	 * in the process of stopping.
  392   	 */
  393   	public boolean isAcceptMessagesWhileStopping() {
  394   		return this.acceptMessagesWhileStopping;
  395   	}
  396   
  397   	protected void validateConfiguration() {
  398   		if (this.destination == null) {
  399   			throw new IllegalArgumentException("Property 'destination' or 'destinationName' is required");
  400   		}
  401   		if (isSubscriptionDurable() && !isPubSubDomain()) {
  402   			throw new IllegalArgumentException("A durable subscription requires a topic (pub-sub domain)");
  403   		}
  404   	}
  405   
  406   
  407   	//-------------------------------------------------------------------------
  408   	// Template methods for listener execution
  409   	//-------------------------------------------------------------------------
  410   
  411   	/**
  412   	 * Execute the specified listener,
  413   	 * committing or rolling back the transaction afterwards (if necessary).
  414   	 * @param session the JMS Session to operate on
  415   	 * @param message the received JMS Message
  416   	 * @see #invokeListener
  417   	 * @see #commitIfNecessary
  418   	 * @see #rollbackOnExceptionIfNecessary
  419   	 * @see #handleListenerException
  420   	 */
  421   	protected void executeListener(Session session, Message message) {
  422   		try {
  423   			doExecuteListener(session, message);
  424   		}
  425   		catch (Throwable ex) {
  426   			handleListenerException(ex);
  427   		}
  428   	}
  429   
  430   	/**
  431   	 * Execute the specified listener,
  432   	 * committing or rolling back the transaction afterwards (if necessary).
  433   	 * @param session the JMS Session to operate on
  434   	 * @param message the received JMS Message
  435   	 * @throws JMSException if thrown by JMS API methods
  436   	 * @see #invokeListener
  437   	 * @see #commitIfNecessary
  438   	 * @see #rollbackOnExceptionIfNecessary
  439   	 * @see #convertJmsAccessException
  440   	 */
  441   	protected void doExecuteListener(Session session, Message message) throws JMSException {
  442   		if (!isAcceptMessagesWhileStopping() && !isRunning()) {
  443   			if (logger.isWarnEnabled()) {
  444   				logger.warn("Rejecting received message because of the listener container " +
  445   						"having been stopped in the meantime: " + message);
  446   			}
  447   			rollbackIfNecessary(session);
  448   			throw new MessageRejectedWhileStoppingException();
  449   		}
  450   		try {
  451   			invokeListener(session, message);
  452   		}
  453   		catch (JMSException ex) {
  454   			rollbackOnExceptionIfNecessary(session, ex);
  455   			throw ex;
  456   		}
  457   		catch (RuntimeException ex) {
  458   			rollbackOnExceptionIfNecessary(session, ex);
  459   			throw ex;
  460   		}
  461   		catch (Error err) {
  462   			rollbackOnExceptionIfNecessary(session, err);
  463   			throw err;
  464   		}
  465   		commitIfNecessary(session, message);
  466   	}
  467   
  468   	/**
  469   	 * Invoke the specified listener: either as standard JMS MessageListener
  470   	 * or (preferably) as Spring SessionAwareMessageListener.
  471   	 * @param session the JMS Session to operate on
  472   	 * @param message the received JMS Message
  473   	 * @throws JMSException if thrown by JMS API methods
  474   	 * @see #setMessageListener
  475   	 */
  476   	protected void invokeListener(Session session, Message message) throws JMSException {
  477   		Object listener = getMessageListener();
  478   		if (listener instanceof SessionAwareMessageListener) {
  479   			doInvokeListener((SessionAwareMessageListener) listener, session, message);
  480   		}
  481   		else if (listener instanceof MessageListener) {
  482   			doInvokeListener((MessageListener) listener, message);
  483   		}
  484   		else if (listener != null) {
  485   			throw new IllegalArgumentException(
  486   					"Only MessageListener and SessionAwareMessageListener supported: " + listener);
  487   		}
  488   		else {
  489   			throw new IllegalStateException("No message listener specified - see property 'messageListener'");
  490   		}
  491   	}
  492   
  493   	/**
  494   	 * Invoke the specified listener as Spring SessionAwareMessageListener,
  495   	 * exposing a new JMS Session (potentially with its own transaction)
  496   	 * to the listener if demanded.
  497   	 * @param listener the Spring SessionAwareMessageListener to invoke
  498   	 * @param session the JMS Session to operate on
  499   	 * @param message the received JMS Message
  500   	 * @throws JMSException if thrown by JMS API methods
  501   	 * @see SessionAwareMessageListener
  502   	 * @see #setExposeListenerSession
  503   	 */
  504   	protected void doInvokeListener(SessionAwareMessageListener listener, Session session, Message message)
  505   			throws JMSException {
  506   
  507   		Connection conToClose = null;
  508   		Session sessionToClose = null;
  509   		try {
  510   			Session sessionToUse = session;
  511   			if (!isExposeListenerSession()) {
  512   				// We need to expose a separate Session.
  513   				conToClose = createConnection();
  514   				sessionToClose = createSession(conToClose);
  515   				sessionToUse = sessionToClose;
  516   			}
  517   			// Actually invoke the message listener...
  518   			listener.onMessage(message, sessionToUse);
  519   			// Clean up specially exposed Session, if any.
  520   			if (sessionToUse != session) {
  521   				if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) {
  522   					// Transacted session created by this container -> commit.
  523   					JmsUtils.commitIfNecessary(sessionToUse);
  524   				}
  525   			}
  526   		}
  527   		finally {
  528   			JmsUtils.closeSession(sessionToClose);
  529   			JmsUtils.closeConnection(conToClose);
  530   		}
  531   	}
  532   
  533   	/**
  534   	 * Invoke the specified listener as standard JMS MessageListener.
  535   	 * <p>Default implementation performs a plain invocation of the
  536   	 * <code>onMessage</code> method.
  537   	 * @param listener the JMS MessageListener to invoke
  538   	 * @param message the received JMS Message
  539   	 * @throws JMSException if thrown by JMS API methods
  540   	 * @see javax.jms.MessageListener#onMessage
  541   	 */
  542   	protected void doInvokeListener(MessageListener listener, Message message) throws JMSException {
  543   		listener.onMessage(message);
  544   	}
  545   
  546   	/**
  547   	 * Perform a commit or message acknowledgement, as appropriate.
  548   	 * @param session the JMS Session to commit
  549   	 * @param message the Message to acknowledge
  550   	 * @throws javax.jms.JMSException in case of commit failure
  551   	 */
  552   	protected void commitIfNecessary(Session session, Message message) throws JMSException {
  553   		// Commit session or acknowledge message.
  554   		if (session.getTransacted()) {
  555   			// Commit necessary - but avoid commit call within a JTA transaction.
  556   			if (isSessionLocallyTransacted(session)) {
  557   				// Transacted session created by this container -> commit.
  558   				JmsUtils.commitIfNecessary(session);
  559   			}
  560   		}
  561   		else if (isClientAcknowledge(session)) {
  562   			message.acknowledge();
  563   		}
  564   	}
  565   
  566   	/**
  567   	 * Perform a rollback, if appropriate.
  568   	 * @param session the JMS Session to rollback
  569   	 * @throws javax.jms.JMSException in case of a rollback error
  570   	 */
  571   	protected void rollbackIfNecessary(Session session) throws JMSException {
  572   		if (session.getTransacted() && isSessionLocallyTransacted(session)) {
  573   			// Transacted session created by this container -> rollback.
  574   			JmsUtils.rollbackIfNecessary(session);
  575   		}
  576   	}
  577   
  578   	/**
  579   	 * Perform a rollback, handling rollback exceptions properly.
  580   	 * @param session the JMS Session to rollback
  581   	 * @param ex the thrown application exception or error
  582   	 * @throws javax.jms.JMSException in case of a rollback error
  583   	 */
  584   	protected void rollbackOnExceptionIfNecessary(Session session, Throwable ex) throws JMSException {
  585   		try {
  586   			if (session.getTransacted() && isSessionLocallyTransacted(session)) {
  587   				// Transacted session created by this container -> rollback.
  588   				if (logger.isDebugEnabled()) {
  589   					logger.debug("Initiating transaction rollback on application exception", ex);
  590   				}
  591   				JmsUtils.rollbackIfNecessary(session);
  592   			}
  593   		}
  594   		catch (IllegalStateException ex2) {
  595   			logger.debug("Could not roll back because Session already closed", ex2);
  596   		}
  597   		catch (JMSException ex2) {
  598   			logger.error("Application exception overridden by rollback exception", ex);
  599   			throw ex2;
  600   		}
  601   		catch (RuntimeException ex2) {
  602   			logger.error("Application exception overridden by rollback exception", ex);
  603   			throw ex2;
  604   		}
  605   		catch (Error err) {
  606   			logger.error("Application exception overridden by rollback error", ex);
  607   			throw err;
  608   		}
  609   	}
  610   
  611   	/**
  612   	 * Check whether the given Session is locally transacted, that is, whether
  613   	 * its transaction is managed by this listener container's Session handling
  614   	 * and not by an external transaction coordinator.
  615   	 * <p>Note: The Session's own transacted flag will already have been checked
  616   	 * before. This method is about finding out whether the Session's transaction
  617   	 * is local or externally coordinated.
  618   	 * @param session the Session to check
  619   	 * @return whether the given Session is locally transacted
  620   	 * @see #isSessionTransacted()
  621   	 * @see org.springframework.jms.connection.ConnectionFactoryUtils#isSessionTransactional
  622   	 */
  623   	protected boolean isSessionLocallyTransacted(Session session) {
  624   		return isSessionTransacted();
  625   	}
  626   
  627   	/**
  628   	 * Handle the given exception that arose during listener execution.
  629   	 * <p>The default implementation logs the exception at error level,
  630   	 * not propagating it to the JMS provider - assuming that all handling of
  631   	 * acknowledgement and/or transactions is done by this listener container.
  632   	 * This can be overridden in subclasses.
  633   	 * @param ex the exception to handle
  634   	 */
  635   	protected void handleListenerException(Throwable ex) {
  636   		if (ex instanceof MessageRejectedWhileStoppingException) {
  637   			// Internal exception - has been handled before.
  638   			return;
  639   		}
  640   		if (ex instanceof JMSException) {
  641   			invokeExceptionListener((JMSException) ex);
  642   		}
  643   		if (isActive()) {
  644   			// Regular case: failed while active.
  645   			// Log at error level.
  646   			logger.warn("Execution of JMS message listener failed", ex);
  647   		}
  648   		else {
  649   			// Rare case: listener thread failed after container shutdown.
  650   			// Log at debug level, to avoid spamming the shutdown log.
  651   			logger.debug("Listener exception after container shutdown", ex);
  652   		}
  653   	}
  654   
  655   	/**
  656   	 * Invoke the registered JMS ExceptionListener, if any.
  657   	 * @param ex the exception that arose during JMS processing
  658   	 * @see #setExceptionListener
  659   	 */
  660   	protected void invokeExceptionListener(JMSException ex) {
  661   		ExceptionListener exceptionListener = getExceptionListener();
  662   		if (exceptionListener != null) {
  663   			exceptionListener.onException(ex);
  664   		}
  665   	}
  666   
  667   
  668   	/**
  669   	 * Internal exception class that indicates a rejected message on shutdown.
  670   	 * Used to trigger a rollback for an external transaction manager in that case.
  671   	 */
  672   	private static class MessageRejectedWhileStoppingException extends RuntimeException {
  673   
  674   	}
  675   
  676   }

Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » jms » listener » [javadoc | source]