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

bitronix.tm.resource.jms.DualSessionWrapper Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * Copyright (C) 2006-2013 Bitronix Software (http://www.bitronix.be)
 *
 * 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 bitronix.tm.resource.jms;

import bitronix.tm.BitronixTransaction;
import bitronix.tm.internal.BitronixRollbackSystemException;
import bitronix.tm.internal.BitronixSystemException;
import bitronix.tm.internal.LogDebugCheck;
import bitronix.tm.resource.common.AbstractXAResourceHolder;
import bitronix.tm.resource.common.ResourceBean;
import bitronix.tm.resource.common.StateChangeListener;
import bitronix.tm.resource.common.TransactionContextHelper;

import jakarta.jms.*;
import jakarta.jms.IllegalStateException;
import jakarta.transaction.RollbackException;
import jakarta.transaction.SystemException;
import javax.transaction.xa.XAResource;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;

/**
 * JMS Session wrapper that will send calls to either a XASession or to a non-XA Session depending on the calling
 * context.
 *
 * @author Ludovic Orban
 */
public class DualSessionWrapper
		extends AbstractXAResourceHolder
		implements Session, StateChangeListener
{

	private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(DualSessionWrapper.class.toString());
	private static final String CREATING_IT = ", creating it";
	private static final String FOUND_CONSUMER_ON = "found consumer based on ";

	private final JmsPooledConnection pooledConnection;
	private final boolean transacted;
	private final int acknowledgeMode;
	private final Map messageProducers = new HashMap<>();
	private final Map messageConsumers = new HashMap<>();
	private final Map topicSubscribers = new HashMap<>();
	private XASession xaSession;
	private Session session;
	private XAResource xaResource;
	private MessageListener listener;

	/**
	 * Constructor DualSessionWrapper creates a new DualSessionWrapper instance.
	 *
	 * @param pooledConnection
	 * 		of type JmsPooledConnection
	 * @param transacted
	 * 		of type boolean
	 * @param acknowledgeMode
	 * 		of type int
	 */
	public DualSessionWrapper(JmsPooledConnection pooledConnection, boolean transacted, int acknowledgeMode)
	{
		this.pooledConnection = pooledConnection;
		this.transacted = transacted;
		this.acknowledgeMode = acknowledgeMode;

		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer("getting session handle from " + pooledConnection);
		}
		setState(State.ACCESSIBLE);
		addStateChangeEventListener(this);
	}

	/**
	 * Method toString ...
	 *
	 * @return String
	 */
	@Override
	public String toString()
	{
		return "a DualSessionWrapper in state " + getState() + " of " + pooledConnection;
	}

	/**
	 * Method stateChanged ...
	 *
	 * @param source
	 * 		of type DualSessionWrapper
	 * @param oldState
	 * 		of type State
	 * @param newState
	 * 		of type State
	 */
	/*
	 * When the session is closed (directly or deferred) the action is to change its state to IN_POOL.
	 * There is no such state for JMS sessions, this just means that it has been closed -> force a
	 * state switch to CLOSED then clean up.
	 */
	@Override
	public void stateChanged(DualSessionWrapper source, State oldState, State newState)
	{
		if (newState == State.IN_POOL)
		{
			setState(State.CLOSED);
		}
		else if (newState == State.CLOSED)
		{
			if (LogDebugCheck.isDebugEnabled())
			{
				log.finer("session state changing to CLOSED, cleaning it up: " + this);
			}

			if (xaSession != null)
			{
				try
				{
					xaSession.close();
				}
				catch (JMSException ex)
				{
					log.log(Level.SEVERE, "error closing XA session", ex);
				}
				xaSession = null;
				xaResource = null;
			}

			if (session != null)
			{
				try
				{
					session.close();
				}
				catch (JMSException ex)
				{
					log.log(Level.SEVERE, "error closing session", ex);
				}
				session = null;
			}

			for (Entry entry : messageProducers.entrySet())
			{
				MessageProducerWrapper messageProducerWrapper = (MessageProducerWrapper) entry.getValue();
				try
				{
					messageProducerWrapper.close();
				}
				catch (JMSException ex)
				{
					log.log(Level.SEVERE, "error closing message producer", ex);
				}
			}
			messageProducers.clear();
			for (Entry entry : messageConsumers.entrySet())
			{
				MessageConsumerWrapper messageConsumerWrapper = (MessageConsumerWrapper) entry.getValue();
				try
				{
					messageConsumerWrapper.close();
				}
				catch (JMSException ex)
				{
					log.log(Level.SEVERE, "error closing message consumer", ex);
				}
			}
			messageConsumers.clear();

		} // if newState == State.CLOSED
	}

	/**
	 * Method stateChanging ...
	 *
	 * @param source
	 * 		of type DualSessionWrapper
	 * @param currentState
	 * 		of type State
	 * @param futureState
	 * 		of type State
	 */
	@Override
	public void stateChanging(DualSessionWrapper source, State currentState, State futureState)
	{
		//No config required
	}

	/**
	 * Get the vendor's {@link javax.transaction.xa.XAResource} implementation of the wrapped resource.
	 *
	 * @return the vendor's XAResource implementation.
	 */
	@Override
	public XAResource getXAResource()
	{
		return xaResource;
	}

	/**
	 * Get the ResourceBean which created this XAResourceHolder.
	 *
	 * @return the ResourceBean which created this XAResourceHolder.
	 */
	@Override
	public ResourceBean getResourceBean()
	{
		return getPoolingConnectionFactory();
	}

	/**
	 * Method getPoolingConnectionFactory returns the poolingConnectionFactory of this DualSessionWrapper object.
	 *
	 * @return the poolingConnectionFactory (type PoolingConnectionFactory) of this DualSessionWrapper object.
	 */
	public PoolingConnectionFactory getPoolingConnectionFactory()
	{
		return pooledConnection.getPoolingConnectionFactory();
	}


	/* wrapped Session methods that have special XA semantics */

	/**
	 * Get the list of {@link bitronix.tm.resource.common.XAResourceHolder}s created by this
	 * {@link bitronix.tm.resource.common.XAStatefulHolder} that are still open.
	 * 

This method is thread-safe.

* * @return the list of {@link bitronix.tm.resource.common.XAResourceHolder}s created by this * {@link bitronix.tm.resource.common.XAStatefulHolder} that are still open. */ @Override public List getXAResourceHolders() { return Collections.singletonList(this); } /** * Create a disposable handler used to drive a pooled instance of * {@link bitronix.tm.resource.common.XAStatefulHolder}. *

This method is thread-safe.

* * @return a resource-specific disposable connection object. * * @throws Exception * a resource-specific exception thrown when the disposable connection cannot be created. */ @Override public Object getConnectionHandle() throws Exception { return null; } /** * Close the physical connection that this {@link bitronix.tm.resource.common.XAStatefulHolder} represents. * * @throws JMSException * a resource-specific exception thrown when there is an error closing the physical connection. */ @Override public void close() throws JMSException { if (getState() != State.ACCESSIBLE) { if (LogDebugCheck.isDebugEnabled()) { log.finer("not closing already closed " + this); } return; } if (LogDebugCheck.isDebugEnabled()) { log.finer("closing " + this); } // delisting try { TransactionContextHelper.delistFromCurrentTransaction(this); } catch (BitronixRollbackSystemException ex) { throw (JMSException) new TransactionRolledBackException("unilateral rollback of " + this).initCause(ex); } catch (SystemException ex) { throw (JMSException) new JMSException("error delisting " + this).initCause(ex); } finally { // requeuing try { TransactionContextHelper.requeue(this, pooledConnection.getPoolingConnectionFactory()); } catch (BitronixSystemException ex) { // this may hide the exception thrown by delistFromCurrentTransaction() but // an error requeuing must absolutely be reported as an exception. // Too bad if this happens... See JdbcPooledConnection.release(JmsPooledConnection) as well. throw (JMSException) new JMSException("error requeuing " + this).initCause(ex); } } } /** * Get the date at which this object was last released to the pool. This is required to check if it is eligible * for discard when the containing pool needs to shrink. * * @return the date at which this object was last released to the pool or null if it never left the pool. */ @Override public Date getLastReleaseDate() { return null; } /** * Method getXAResourceHolderForXaResource ... * * @param xaResource * of type XAResource * * @return DualSessionWrapper */ public DualSessionWrapper getXAResourceHolderForXaResource(XAResource xaResource) { if (xaResource == this.xaResource) { return this; } return null; } /** * Method createBytesMessage ... * * @return BytesMessage * * @throws JMSException * when */ @Override public BytesMessage createBytesMessage() throws JMSException { return getSession().createBytesMessage(); } /** * Method createMapMessage ... * * @return MapMessage * * @throws JMSException * when */ @Override public MapMessage createMapMessage() throws JMSException { return getSession().createMapMessage(); } /** * Method createMessage ... * * @return Message * * @throws JMSException * when */ @Override public Message createMessage() throws JMSException { return getSession().createMessage(); } /** * Method createObjectMessage ... * * @return ObjectMessage * * @throws JMSException * when */ @Override public ObjectMessage createObjectMessage() throws JMSException { return getSession().createObjectMessage(); } /** * Method createObjectMessage ... * * @param serializable * of type Serializable * * @return ObjectMessage * * @throws JMSException * when */ @Override public ObjectMessage createObjectMessage(Serializable serializable) throws JMSException { return getSession().createObjectMessage(serializable); } /** * Method createStreamMessage ... * * @return StreamMessage * * @throws JMSException * when */ @Override public StreamMessage createStreamMessage() throws JMSException { return getSession().createStreamMessage(); } /** * Method createTextMessage ... * * @return TextMessage * * @throws JMSException * when */ @Override public TextMessage createTextMessage() throws JMSException { return getSession().createTextMessage(); } /** * Method createTextMessage ... * * @param text * of type String * * @return TextMessage * * @throws JMSException * when */ @Override public TextMessage createTextMessage(String text) throws JMSException { return getSession().createTextMessage(text); } /* XAResourceHolder implementation */ /** * Method getTransacted returns the transacted of this DualSessionWrapper object. * * @return the transacted (type boolean) of this DualSessionWrapper object. * * @throws JMSException * when */ @Override public boolean getTransacted() throws JMSException { if (isParticipatingInActiveGlobalTransaction()) { return true; // for consistency with EJB 2.1 spec (17.3.5) } return getSession().getTransacted(); } /** * Method getAcknowledgeMode returns the acknowledgeMode of this DualSessionWrapper object. * * @return the acknowledgeMode (type int) of this DualSessionWrapper object. * * @throws JMSException * when */ @Override public int getAcknowledgeMode() throws JMSException { if (isParticipatingInActiveGlobalTransaction()) { return 0; // for consistency with EJB 2.1 spec (17.3.5) } return getSession().getAcknowledgeMode(); } /* XAStatefulHolder implementation */ /** * Method commit ... * * @throws JMSException * when */ @Override public void commit() throws JMSException { if (isParticipatingInActiveGlobalTransaction()) { throw new TransactionInProgressException("cannot commit a resource enlisted in a global transaction"); } getSession().commit(); } /** * Method rollback ... * * @throws JMSException * when */ @Override public void rollback() throws JMSException { if (isParticipatingInActiveGlobalTransaction()) { throw new TransactionInProgressException("cannot rollback a resource enlisted in a global transaction"); } getSession().rollback(); } /** * Method recover ... * * @throws JMSException * when */ @Override public void recover() throws JMSException { if (isParticipatingInActiveGlobalTransaction()) { throw new TransactionInProgressException("cannot recover a resource enlisted in a global transaction"); } getSession().recover(); } /* XA-enhanced methods */ /** * Method getMessageListener returns the messageListener of this DualSessionWrapper object. * * @return the messageListener (type MessageListener) of this DualSessionWrapper object. * * @throws JMSException * when */ @Override public MessageListener getMessageListener() throws JMSException { return listener; } /** * Method setMessageListener sets the messageListener of this DualSessionWrapper object. * * @param listener * the messageListener of this DualSessionWrapper object. * * @throws JMSException * when */ @Override public void setMessageListener(MessageListener listener) throws JMSException { if (getState() == State.CLOSED) { throw new IllegalStateException("session handle is closed"); } if (session != null) { session.setMessageListener(listener); } if (xaSession != null) { xaSession.setMessageListener(listener); } this.listener = listener; } /** * Method run ... */ @Override public void run() { try { Session internalSession = getSession(true); if (LogDebugCheck.isDebugEnabled()) { log.finer("running XA session " + internalSession); } internalSession.run(); } catch (JMSException ex) { log.log(Level.SEVERE, "error getting session", ex); } } /** * Method createProducer ... * * @param destination * of type Destination * * @return MessageProducer * * @throws JMSException * when */ @Override public MessageProducer createProducer(Destination destination) throws JMSException { MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination); if (LogDebugCheck.isDebugEnabled()) { log.finer("looking for producer based on " + key); } MessageProducerWrapper messageProducer = (MessageProducerWrapper) messageProducers.get(key); if (messageProducer == null) { if (LogDebugCheck.isDebugEnabled()) { log.finer("found no producer based on " + key + CREATING_IT); } messageProducer = new MessageProducerWrapper(getSession().createProducer(destination), this, pooledConnection.getPoolingConnectionFactory()); if (pooledConnection.getPoolingConnectionFactory() .getCacheProducersConsumers()) { if (LogDebugCheck.isDebugEnabled()) { log.finer("caching producer via key " + key); } messageProducers.put(key, messageProducer); } } else if (LogDebugCheck.isDebugEnabled()) { log.finer("found producer based on " + key + ", recycling it: " + messageProducer); } return messageProducer; } /** * Method createConsumer ... * * @param destination * of type Destination * * @return MessageConsumer * * @throws JMSException * when */ @Override public MessageConsumer createConsumer(Destination destination) throws JMSException { MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination); if (LogDebugCheck.isDebugEnabled()) { log.finer("looking for consumer based on " + key); } MessageConsumerWrapper messageConsumer = (MessageConsumerWrapper) messageConsumers.get(key); if (messageConsumer == null) { if (LogDebugCheck.isDebugEnabled()) { log.finer("found no consumer based on " + key + CREATING_IT); } messageConsumer = new MessageConsumerWrapper(getSession().createConsumer(destination), this, pooledConnection.getPoolingConnectionFactory()); if (pooledConnection.getPoolingConnectionFactory() .getCacheProducersConsumers()) { if (LogDebugCheck.isDebugEnabled()) { log.finer("caching consumer via key " + key); } messageConsumers.put(key, messageConsumer); } } else if (LogDebugCheck.isDebugEnabled()) { log.finer(FOUND_CONSUMER_ON + key + ", recycling it: " + messageConsumer); } return messageConsumer; } /** * Method createConsumer ... * * @param destination * of type Destination * @param messageSelector * of type String * * @return MessageConsumer * * @throws JMSException * when */ @Override public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException { MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination, messageSelector); if (LogDebugCheck.isDebugEnabled()) { log.finer("looking for consumer based on " + key); } MessageConsumerWrapper messageConsumer = (MessageConsumerWrapper) messageConsumers.get(key); if (messageConsumer == null) { if (LogDebugCheck.isDebugEnabled()) { log.finer("found no consumer based on " + key + CREATING_IT); } messageConsumer = new MessageConsumerWrapper(getSession().createConsumer(destination, messageSelector), this, pooledConnection.getPoolingConnectionFactory()); if (pooledConnection.getPoolingConnectionFactory() .getCacheProducersConsumers()) { if (LogDebugCheck.isDebugEnabled()) { log.finer("caching consumer via key " + key); } messageConsumers.put(key, messageConsumer); } } else if (LogDebugCheck.isDebugEnabled()) { log.finer(FOUND_CONSUMER_ON + key + ", recycling it: " + messageConsumer); } return messageConsumer; } /** * Method createConsumer ... * * @param destination * of type Destination * @param messageSelector * of type String * @param noLocal * of type boolean * * @return MessageConsumer * * @throws JMSException * when */ @Override public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal) throws JMSException { MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination, messageSelector, noLocal); if (LogDebugCheck.isDebugEnabled()) { log.finer("looking for consumer based on " + key); } MessageConsumerWrapper messageConsumer = (MessageConsumerWrapper) messageConsumers.get(key); if (messageConsumer == null) { if (LogDebugCheck.isDebugEnabled()) { log.finer("found no consumer based on " + key + CREATING_IT); } messageConsumer = new MessageConsumerWrapper(getSession().createConsumer(destination, messageSelector, noLocal), this, pooledConnection.getPoolingConnectionFactory()); if (pooledConnection.getPoolingConnectionFactory() .getCacheProducersConsumers()) { if (LogDebugCheck.isDebugEnabled()) { log.finer("caching consumer via key " + key); } messageConsumers.put(key, messageConsumer); } } else if (LogDebugCheck.isDebugEnabled()) { log.finer(FOUND_CONSUMER_ON + key + ", recycling it: " + messageConsumer); } return messageConsumer; } /* dumb wrapping of Session methods */ /** * Method createQueue ... * * @param queueName * of type String * * @return Queue * * @throws JMSException * when */ @Override public jakarta.jms.Queue createQueue(String queueName) throws JMSException { return getSession().createQueue(queueName); } /** * Method createTopic ... * * @param topicName * of type String * * @return Topic * * @throws JMSException * when */ @Override public Topic createTopic(String topicName) throws JMSException { return getSession().createTopic(topicName); } /** * Method createDurableSubscriber ... * * @param topic * of type Topic * @param name * of type String * * @return TopicSubscriber * * @throws JMSException * when */ @Override public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException { MessageProducerConsumerKey key = new MessageProducerConsumerKey(topic); if (LogDebugCheck.isDebugEnabled()) { log.finer("looking for durable subscriber based on " + key); } TopicSubscriberWrapper topicSubscriber = topicSubscribers.get(key); if (topicSubscriber == null) { if (LogDebugCheck.isDebugEnabled()) { log.finer("found no durable subscriber based on " + key + CREATING_IT); } topicSubscriber = new TopicSubscriberWrapper(getSession().createDurableSubscriber(topic, name), this, pooledConnection.getPoolingConnectionFactory()); if (pooledConnection.getPoolingConnectionFactory() .getCacheProducersConsumers()) { if (LogDebugCheck.isDebugEnabled()) { log.finer("caching durable subscriber via key " + key); } topicSubscribers.put(key, topicSubscriber); } } else if (LogDebugCheck.isDebugEnabled()) { log.finer("found durable subscriber based on " + key + ", recycling it: " + topicSubscriber); } return topicSubscriber; } /** * Method createDurableSubscriber ... * * @param topic * of type Topic * @param name * of type String * @param messageSelector * of type String * @param noLocal * of type boolean * * @return TopicSubscriber * * @throws JMSException * when */ @Override public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException { MessageProducerConsumerKey key = new MessageProducerConsumerKey(topic, messageSelector, noLocal); if (LogDebugCheck.isDebugEnabled()) { log.finer("looking for durable subscriber based on " + key); } TopicSubscriberWrapper topicSubscriber = topicSubscribers.get(key); if (topicSubscriber == null) { if (LogDebugCheck.isDebugEnabled()) { log.finer("found no durable subscriber based on " + key + CREATING_IT); } topicSubscriber = new TopicSubscriberWrapper(getSession().createDurableSubscriber(topic, name, messageSelector, noLocal), this, pooledConnection.getPoolingConnectionFactory()); if (pooledConnection.getPoolingConnectionFactory() .getCacheProducersConsumers()) { if (LogDebugCheck.isDebugEnabled()) { log.finer("caching durable subscriber via key " + key); } topicSubscribers.put(key, topicSubscriber); } } else if (LogDebugCheck.isDebugEnabled()) { log.finer("found durable subscriber based on " + key + ", recycling it: " + topicSubscriber); } return topicSubscriber; } /** * Method createBrowser ... * * @param queue * of type Queue * * @return QueueBrowser * * @throws JMSException * when */ @Override public QueueBrowser createBrowser(jakarta.jms.Queue queue) throws JMSException { enlistResource(); return getSession().createBrowser(queue); } /** * Method createBrowser ... * * @param queue * of type Queue * @param messageSelector * of type String * * @return QueueBrowser * * @throws JMSException * when */ @Override public QueueBrowser createBrowser(jakarta.jms.Queue queue, String messageSelector) throws JMSException { enlistResource(); return getSession().createBrowser(queue, messageSelector); } /** * Method createTemporaryQueue ... * * @return TemporaryQueue * * @throws JMSException * when */ @Override public TemporaryQueue createTemporaryQueue() throws JMSException { return getSession().createTemporaryQueue(); } /** * Method createTemporaryTopic ... * * @return TemporaryTopic * * @throws JMSException * when */ @Override public TemporaryTopic createTemporaryTopic() throws JMSException { return getSession().createTemporaryTopic(); } /** * Method unsubscribe ... * * @param name * of type String * * @throws JMSException * when */ @Override public void unsubscribe(String name) throws JMSException { getSession().unsubscribe(name); } /** * Method getSession returns the session of this DualSessionWrapper object. * * @return the session (type Session) of this DualSessionWrapper object. * * @throws JMSException * when */ public Session getSession() throws JMSException { return getSession(false); } /** * Method getSession ... * * @param forceXa * of type boolean * * @return Session * * @throws JMSException * when */ public Session getSession(boolean forceXa) throws JMSException { if (getState() == State.CLOSED) { throw new IllegalStateException("session handle is closed"); } if (forceXa) { if (LogDebugCheck.isDebugEnabled()) { log.finer("choosing XA session (forced)"); } return createXASession(); } else { BitronixTransaction currentTransaction = TransactionContextHelper.currentTransaction(); if (currentTransaction != null) { if (LogDebugCheck.isDebugEnabled()) { log.finer("choosing XA session"); } return createXASession(); } if (LogDebugCheck.isDebugEnabled()) { log.finer("choosing non-XA session"); } return createNonXASession(); } } /** * Method createXASession ... * * @return Session * * @throws JMSException * when */ private Session createXASession() throws JMSException { // XA if (xaSession == null) { xaSession = pooledConnection.getXAConnection() .createXASession(); if (listener != null) { xaSession.setMessageListener(listener); if (LogDebugCheck.isDebugEnabled()) { log.finer("get XA session registered message listener: " + listener); } } xaResource = xaSession.getXAResource(); } return xaSession.getSession(); } /** * Method createNonXASession ... * * @return Session * * @throws JMSException * when */ private Session createNonXASession() throws JMSException { // non-XA if (session == null) { session = pooledConnection.getXAConnection() .createSession(transacted, acknowledgeMode); if (listener != null) { session.setMessageListener(listener); if (LogDebugCheck.isDebugEnabled()) { log.finer("get non-XA session registered message listener: " + listener); } } } return session; } /** * Enlist this session into the current transaction if automaticEnlistingEnabled = true for this resource. * If no transaction is running then this method does nothing. * * @throws JMSException * if an exception occurs */ protected void enlistResource() throws JMSException { PoolingConnectionFactory poolingConnectionFactory = pooledConnection.getPoolingConnectionFactory(); if (poolingConnectionFactory.getAutomaticEnlistingEnabled()) { getSession(); // make sure the session is created before enlisting it try { TransactionContextHelper.enlistInCurrentTransaction(this); } catch (SystemException | RollbackException ex) { throw (JMSException) new JMSException("error enlisting " + this).initCause(ex); } } // if getAutomaticEnlistingEnabled } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy