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