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

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

The 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.resource.common.AbstractXAResourceHolder;
import bitronix.tm.resource.common.ResourceBean;
import bitronix.tm.resource.common.StateChangeListener;
import bitronix.tm.resource.common.TransactionContextHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.IllegalStateException;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import javax.jms.StreamMessage;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import javax.jms.TransactionInProgressException;
import javax.jms.TransactionRolledBackException;
import javax.jms.XASession;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.xa.XAResource;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 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 final static Logger log = LoggerFactory.getLogger(DualSessionWrapper.class);

    private final JmsPooledConnection pooledConnection;
    private final boolean transacted;
    private final int acknowledgeMode;

    private XASession xaSession;
    private Session session;
    private XAResource xaResource;
    private MessageListener listener;

    //TODO: shouldn't producers/consumers/subscribers be separated between XA and non-XA session ?
    private final Map messageProducers = new HashMap();
    private final Map messageConsumers = new HashMap();
    private final Map topicSubscribers = new HashMap();

    public DualSessionWrapper(JmsPooledConnection pooledConnection, boolean transacted, int acknowledgeMode) {
        this.pooledConnection = pooledConnection;
        this.transacted = transacted;
        this.acknowledgeMode = acknowledgeMode;

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

    public PoolingConnectionFactory getPoolingConnectionFactory() {
        return pooledConnection.getPoolingConnectionFactory();
    }

    public Session getSession() throws JMSException {
        return getSession(false);
    }

    public Session getSession(boolean forceXa) throws JMSException {
        if (getState() == State.CLOSED)
            throw new IllegalStateException("session handle is closed");

        if (forceXa) {
            if (log.isDebugEnabled()) { log.debug("choosing XA session (forced)"); }
            return createXASession();
        }
        else {
            BitronixTransaction currentTransaction = TransactionContextHelper.currentTransaction();
            if (currentTransaction != null) {
                if (log.isDebugEnabled()) { log.debug("choosing XA session"); }
                return createXASession();
            }
            if (log.isDebugEnabled()) { log.debug("choosing non-XA session"); }
            return createNonXASession();
        }
    }

    private Session createNonXASession() throws JMSException {
        // non-XA
        if (session == null) {
            session = pooledConnection.getXAConnection().createSession(transacted, acknowledgeMode);
            if (listener != null) {
                session.setMessageListener(listener);
                if (log.isDebugEnabled()) { log.debug("get non-XA session registered message listener: " + listener); }
            }
        }
        return session;
    }

    private Session createXASession() throws JMSException {
        // XA
        if (xaSession == null) {
            xaSession = pooledConnection.getXAConnection().createXASession();
            if (listener != null) {
                xaSession.setMessageListener(listener);
                if (log.isDebugEnabled()) { log.debug("get XA session registered message listener: " + listener); }
            }
            xaResource = xaSession.getXAResource();
        }
        return xaSession.getSession();
    }

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


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

    @Override
    public void close() throws JMSException {
        if (getState() != State.ACCESSIBLE) {
            if (log.isDebugEnabled()) { log.debug("not closing already closed " + this); }
            return;
        }

        if (log.isDebugEnabled()) { log.debug("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() as well.
                throw (JMSException) new JMSException("error requeuing " + this).initCause(ex);
            }
        }

    }

    @Override
    public Date getLastReleaseDate() {
        return null;
    }

    /*
     * 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 (log.isDebugEnabled()) { log.debug("session state changing to CLOSED, cleaning it up: " + this); }

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

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

            Iterator> it = messageProducers.entrySet().iterator();
            while (it.hasNext()) {
                Entry entry = it.next();
                MessageProducerWrapper messageProducerWrapper = (MessageProducerWrapper) entry.getValue();
                try {
                    messageProducerWrapper.close();
                } catch (JMSException ex) {
                    log.error("error closing message producer", ex);
                }
            }
            messageProducers.clear();

            Iterator> it2 = messageConsumers.entrySet().iterator();
            while (it2.hasNext()) {
                Entry entry = it2.next();
                MessageConsumerWrapper messageConsumerWrapper = (MessageConsumerWrapper) entry.getValue();
                try {
                    messageConsumerWrapper.close();
                } catch (JMSException ex) {
                    log.error("error closing message consumer", ex);
                }
            }
            messageConsumers.clear();

        } // if newState == State.CLOSED
    }

    @Override
    public void stateChanging(DualSessionWrapper source, State currentState, State futureState) {
    }

    @Override
    public MessageProducer createProducer(Destination destination) throws JMSException {
        MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination);
        if (log.isDebugEnabled()) { log.debug("looking for producer based on " + key); }
        MessageProducerWrapper messageProducer = (MessageProducerWrapper) messageProducers.get(key);
        if (messageProducer == null) {
            if (log.isDebugEnabled()) { log.debug("found no producer based on " + key + ", creating it"); }
            messageProducer = new MessageProducerWrapper(getSession().createProducer(destination), this, pooledConnection.getPoolingConnectionFactory());

            if (pooledConnection.getPoolingConnectionFactory().getCacheProducersConsumers()) {
                if (log.isDebugEnabled()) { log.debug("caching producer via key " + key); }
                messageProducers.put(key, messageProducer);
            }
        }
        else if (log.isDebugEnabled()) { log.debug("found producer based on " + key + ", recycling it: " + messageProducer); }
        return messageProducer;
    }

    @Override
    public MessageConsumer createConsumer(Destination destination) throws JMSException {
        MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination);
        if (log.isDebugEnabled()) { log.debug("looking for consumer based on " + key); }
        MessageConsumerWrapper messageConsumer = (MessageConsumerWrapper) messageConsumers.get(key);
        if (messageConsumer == null) {
            if (log.isDebugEnabled()) { log.debug("found no consumer based on " + key + ", creating it"); }
            messageConsumer = new MessageConsumerWrapper(getSession().createConsumer(destination), this, pooledConnection.getPoolingConnectionFactory());

            if (pooledConnection.getPoolingConnectionFactory().getCacheProducersConsumers()) {
                if (log.isDebugEnabled()) { log.debug("caching consumer via key " + key); }
                messageConsumers.put(key, messageConsumer);
            }
        }
        else if (log.isDebugEnabled()) { log.debug("found consumer based on " + key + ", recycling it: " + messageConsumer); }
        return messageConsumer;
    }

    @Override
    public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException {
        MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination, messageSelector);
        if (log.isDebugEnabled()) { log.debug("looking for consumer based on " + key); }
        MessageConsumerWrapper messageConsumer = (MessageConsumerWrapper) messageConsumers.get(key);
        if (messageConsumer == null) {
            if (log.isDebugEnabled()) { log.debug("found no consumer based on " + key + ", creating it"); }
            messageConsumer = new MessageConsumerWrapper(getSession().createConsumer(destination, messageSelector), this, pooledConnection.getPoolingConnectionFactory());

            if (pooledConnection.getPoolingConnectionFactory().getCacheProducersConsumers()) {
                if (log.isDebugEnabled()) { log.debug("caching consumer via key " + key); }
                messageConsumers.put(key, messageConsumer);
            }
        }
        else if (log.isDebugEnabled()) { log.debug("found consumer based on " + key + ", recycling it: " + messageConsumer); }
        return messageConsumer;
    }

    @Override
    public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal) throws JMSException {
        MessageProducerConsumerKey key = new MessageProducerConsumerKey(destination, messageSelector, noLocal);
        if (log.isDebugEnabled()) { log.debug("looking for consumer based on " + key); }
        MessageConsumerWrapper messageConsumer = (MessageConsumerWrapper) messageConsumers.get(key);
        if (messageConsumer == null) {
            if (log.isDebugEnabled()) { log.debug("found no consumer based on " + key + ", creating it"); }
            messageConsumer = new MessageConsumerWrapper(getSession().createConsumer(destination, messageSelector, noLocal), this, pooledConnection.getPoolingConnectionFactory());

            if (pooledConnection.getPoolingConnectionFactory().getCacheProducersConsumers()) {
                if (log.isDebugEnabled()) { log.debug("caching consumer via key " + key); }
                messageConsumers.put(key, messageConsumer);
            }
        }
        else if (log.isDebugEnabled()) { log.debug("found consumer based on " + key + ", recycling it: " + messageConsumer); }
        return messageConsumer;
    }

    @Override
    public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException {
        MessageProducerConsumerKey key = new MessageProducerConsumerKey(topic);
        if (log.isDebugEnabled()) { log.debug("looking for durable subscriber based on " + key); }
        TopicSubscriberWrapper topicSubscriber = topicSubscribers.get(key);
        if (topicSubscriber == null) {
            if (log.isDebugEnabled()) { log.debug("found no durable subscriber based on " + key + ", creating it"); }
            topicSubscriber = new TopicSubscriberWrapper(getSession().createDurableSubscriber(topic, name), this, pooledConnection.getPoolingConnectionFactory());

            if (pooledConnection.getPoolingConnectionFactory().getCacheProducersConsumers()) {
                if (log.isDebugEnabled()) { log.debug("caching durable subscriber via key " + key); }
                topicSubscribers.put(key, topicSubscriber);
            }
        }
        else if (log.isDebugEnabled()) { log.debug("found durable subscriber based on " + key + ", recycling it: " + topicSubscriber); }
        return topicSubscriber;
    }

    @Override
    public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException {
        MessageProducerConsumerKey key = new MessageProducerConsumerKey(topic, messageSelector, noLocal);
        if (log.isDebugEnabled()) { log.debug("looking for durable subscriber based on " + key); }
        TopicSubscriberWrapper topicSubscriber = topicSubscribers.get(key);
        if (topicSubscriber == null) {
            if (log.isDebugEnabled()) { log.debug("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 (log.isDebugEnabled()) { log.debug("caching durable subscriber via key " + key); }
                topicSubscribers.put(key, topicSubscriber);
            }
        }
        else if (log.isDebugEnabled()) { log.debug("found durable subscriber based on " + key + ", recycling it: " + topicSubscriber); }
        return topicSubscriber;
    }

    @Override
    public MessageListener getMessageListener() throws JMSException {
        return listener;
    }

    @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;
    }

    @Override
    public void run() {
        try {
            Session session = getSession(true);
            if (log.isDebugEnabled()) { log.debug("running XA session " + session); }
            session.run();
        } catch (JMSException ex) {
            log.error("error getting session", ex);
        }
    }

    /* XAResourceHolder implementation */

    @Override
    public XAResource getXAResource() {
        return xaResource;
    }

    @Override
    public ResourceBean getResourceBean() {
        return getPoolingConnectionFactory();
    }

    /* XAStatefulHolder implementation */

    @Override
    public List getXAResourceHolders() {
        return Collections.singletonList(this);
    }

    public DualSessionWrapper getXAResourceHolderForXaResource(XAResource xaResource) {
        if (xaResource == this.xaResource) {
            return this;
        }
        return null;
    }

    @Override
    public Object getConnectionHandle() throws Exception {
        return null;
    }

    /* XA-enhanced methods */

    @Override
    public boolean getTransacted() throws JMSException {
        if (isParticipatingInActiveGlobalTransaction())
            return true; // for consistency with EJB 2.1 spec (17.3.5)

        return getSession().getTransacted();
    }

    @Override
    public int getAcknowledgeMode() throws JMSException {
        if (isParticipatingInActiveGlobalTransaction())
            return 0; // for consistency with EJB 2.1 spec (17.3.5)

        return getSession().getAcknowledgeMode();
    }

    @Override
    public void commit() throws JMSException {
        if (isParticipatingInActiveGlobalTransaction())
            throw new TransactionInProgressException("cannot commit a resource enlisted in a global transaction");

        getSession().commit();
    }

    @Override
    public void rollback() throws JMSException {
        if (isParticipatingInActiveGlobalTransaction())
            throw new TransactionInProgressException("cannot rollback a resource enlisted in a global transaction");

        getSession().rollback();
    }

    @Override
    public void recover() throws JMSException {
        if (isParticipatingInActiveGlobalTransaction())
            throw new TransactionInProgressException("cannot recover a resource enlisted in a global transaction");

        getSession().recover();
    }

    @Override
    public QueueBrowser createBrowser(javax.jms.Queue queue) throws JMSException {
        enlistResource();
        return getSession().createBrowser(queue);
    }

    @Override
    public QueueBrowser createBrowser(javax.jms.Queue queue, String messageSelector) throws JMSException {
        enlistResource();
        return getSession().createBrowser(queue, messageSelector);
    }

    /* dumb wrapping of Session methods */

    @Override
    public BytesMessage createBytesMessage() throws JMSException {
        return getSession().createBytesMessage();
    }

    @Override
    public MapMessage createMapMessage() throws JMSException {
        return getSession().createMapMessage();
    }

    @Override
    public Message createMessage() throws JMSException {
        return getSession().createMessage();
    }

    @Override
    public ObjectMessage createObjectMessage() throws JMSException {
        return getSession().createObjectMessage();
    }

    @Override
    public ObjectMessage createObjectMessage(Serializable serializable) throws JMSException {
        return getSession().createObjectMessage(serializable);
    }

    @Override
    public StreamMessage createStreamMessage() throws JMSException {
        return getSession().createStreamMessage();
    }

    @Override
    public TextMessage createTextMessage() throws JMSException {
        return getSession().createTextMessage();
    }

    @Override
    public TextMessage createTextMessage(String text) throws JMSException {
        return getSession().createTextMessage(text);
    }

    @Override
    public javax.jms.Queue createQueue(String queueName) throws JMSException {
        return getSession().createQueue(queueName);
    }

    @Override
    public Topic createTopic(String topicName) throws JMSException {
        return getSession().createTopic(topicName);
    }

    @Override
    public TemporaryQueue createTemporaryQueue() throws JMSException {
        return getSession().createTemporaryQueue();
    }

    @Override
    public TemporaryTopic createTemporaryTopic() throws JMSException {
        return getSession().createTemporaryTopic();
    }

    @Override
    public void unsubscribe(String name) throws JMSException {
        getSession().unsubscribe(name);
    }


    /**
     * 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 ex) {
                throw (JMSException) new JMSException("error enlisting " + this).initCause(ex);
            } catch (RollbackException ex) {
                throw (JMSException) new JMSException("error enlisting " + this).initCause(ex);
            }
        } // if getAutomaticEnlistingEnabled
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy