/* * 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.internal.BitronixSystemException; import bitronix.tm.resource.common.AbstractXAStatefulHolder; import bitronix.tm.resource.common.RecoveryXAResourceHolder; import bitronix.tm.resource.common.StateChangeListener; import bitronix.tm.resource.common.TransactionContextHelper; import bitronix.tm.resource.jms.lrc.LrcXAConnectionFactory; import bitronix.tm.utils.ManagementRegistrar; import bitronix.tm.utils.MonotonicClock; import bitronix.tm.utils.Scheduler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.JMSException; import javax.jms.Session; import javax.jms.TemporaryQueue; import javax.jms.XAConnection; import javax.jms.XASession; import javax.transaction.xa.XAResource; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Implementation of a JMS pooled connection wrapping vendor's {@link XAConnection} implementation. * * @author Ludovic Orban * TODO: how can the JMS connection be accurately tested? */ public class JmsPooledConnection extends AbstractXAStatefulHolder<JmsPooledConnection> implements JmsPooledConnectionMBean { private final static Logger log = LoggerFactory.getLogger(JmsPooledConnection.class); private volatile XAConnection xaConnection; private final PoolingConnectionFactory poolingConnectionFactory; private final Set<DualSessionWrapper> sessions = Collections.synchronizedSet(new HashSet<DualSessionWrapper>()); /* management */ private final String jmxName; private volatile Date acquisitionDate; private volatile Date lastReleaseDate; protected JmsPooledConnection(PoolingConnectionFactory poolingConnectionFactory, XAConnection connection) { this.poolingConnectionFactory = poolingConnectionFactory; this.xaConnection = connection; this.lastReleaseDate = new Date(MonotonicClock.currentTimeMillis()); addStateChangeEventListener(new JmsPooledConnectionStateChangeListener()); if (LrcXAConnectionFactory.class.getName().equals(poolingConnectionFactory.getClassName())) { if (log.isDebugEnabled()) { log.debug("emulating XA for resource " + poolingConnectionFactory.getUniqueName() + " - changing twoPcOrderingPosition to ALWAYS_LAST_POSITION"); } poolingConnectionFactory.setTwoPcOrderingPosition(Scheduler.ALWAYS_LAST_POSITION); if (log.isDebugEnabled()) { log.debug("emulating XA for resource " + poolingConnectionFactory.getUniqueName() + " - changing deferConnectionRelease to true"); } poolingConnectionFactory.setDeferConnectionRelease(true); if (log.isDebugEnabled()) { log.debug("emulating XA for resource " + poolingConnectionFactory.getUniqueName() + " - changing useTmJoin to true"); } poolingConnectionFactory.setUseTmJoin(true); } this.jmxName = "bitronix.tm:type=JMS,UniqueName=" + ManagementRegistrar.makeValidName(poolingConnectionFactory.getUniqueName()) + ",Id=" + poolingConnectionFactory.incCreatedResourcesCounter(); ManagementRegistrar.register(jmxName, this); } public XAConnection getXAConnection() { return xaConnection; } public PoolingConnectionFactory getPoolingConnectionFactory() { return poolingConnectionFactory; } public synchronized RecoveryXAResourceHolder createRecoveryXAResourceHolder() throws JMSException { DualSessionWrapper dualSessionWrapper = new DualSessionWrapper(this, false, 0); dualSessionWrapper.getSession(true); // force creation of XASession to allow access to XAResource return new RecoveryXAResourceHolder(dualSessionWrapper); } @Override public synchronized void close() throws JMSException { if (xaConnection != null) { poolingConnectionFactory.unregister(this); setState(State.CLOSED); try { xaConnection.close(); } finally { xaConnection = null; } } } @Override public List<DualSessionWrapper> getXAResourceHolders() { synchronized (sessions) { return new ArrayList<DualSessionWrapper>(sessions); } } @Override public Object getConnectionHandle() throws Exception { if (log.isDebugEnabled()) { log.debug("getting connection handle from " + this); } State oldState = getState(); setState(State.ACCESSIBLE); if (oldState == State.IN_POOL) { if (log.isDebugEnabled()) { log.debug("connection " + xaConnection + " was in state IN_POOL, testing it"); } testXAConnection(); } else { if (log.isDebugEnabled()) { log.debug("connection " + xaConnection + " was in state " + oldState + ", no need to test it"); } } if (log.isDebugEnabled()) { log.debug("got connection handle from " + this); } return new JmsConnectionHandle(this, xaConnection); } private void testXAConnection() throws JMSException { if (!poolingConnectionFactory.getTestConnections()) { if (log.isDebugEnabled()) { log.debug("not testing connection of " + this); } return; } if (log.isDebugEnabled()) { log.debug("testing connection of " + this); } XASession xaSession = xaConnection.createXASession(); try { TemporaryQueue tq = xaSession.createTemporaryQueue(); tq.delete(); } finally { xaSession.close(); } } protected void release() throws JMSException { if (log.isDebugEnabled()) { log.debug("releasing to pool " + this); } closePendingSessions(); // requeuing try { TransactionContextHelper.requeue(this, poolingConnectionFactory); } catch (BitronixSystemException ex) { throw (JMSException) new JMSException("error requeueing " + this).initCause(ex); } if (log.isDebugEnabled()) { log.debug("released to pool " + this); } } private void closePendingSessions() { synchronized (sessions) { for (DualSessionWrapper dualSessionWrapper : sessions) { if (dualSessionWrapper.getState() != State.ACCESSIBLE) continue; try { if (log.isDebugEnabled()) { log.debug("trying to close pending session " + dualSessionWrapper); } dualSessionWrapper.close(); } catch (JMSException ex) { log.warn("error closing pending session " + dualSessionWrapper, ex); } } } } protected Session createSession(boolean transacted, int acknowledgeMode) throws JMSException { synchronized (sessions) { DualSessionWrapper sessionHandle = getNotAccessibleSession(); if (sessionHandle == null) { if (log.isDebugEnabled()) { log.debug("no session handle found in NOT_ACCESSIBLE state, creating new session"); } sessionHandle = new DualSessionWrapper(this, transacted, acknowledgeMode); sessionHandle.addStateChangeEventListener(new JmsConnectionHandleStateChangeListener()); sessions.add(sessionHandle); } else { if (log.isDebugEnabled()) { log.debug("found session handle in NOT_ACCESSIBLE state, recycling it: " + sessionHandle); } sessionHandle.setState(State.ACCESSIBLE); } return sessionHandle; } } private DualSessionWrapper getNotAccessibleSession() { synchronized (sessions) { if (log.isDebugEnabled()) { log.debug(sessions.size() + " session(s) open from " + this); } for (DualSessionWrapper sessionHandle : sessions) { if (sessionHandle.getState() == State.NOT_ACCESSIBLE) return sessionHandle; } return null; } } @Override public Date getLastReleaseDate() { return lastReleaseDate; } @Override public String toString() { return "a JmsPooledConnection of pool " + poolingConnectionFactory.getUniqueName() + " in state " + getState() + " with underlying connection " + xaConnection; } /* management */ @Override public String getStateDescription() { return getState().toString(); } @Override public Date getAcquisitionDate() { return acquisitionDate; } @Override public Collection<String> getTransactionGtridsCurrentlyHoldingThis() { synchronized (sessions) { Set<String> result = new HashSet<String>(); for (DualSessionWrapper dsw : sessions) { result.addAll(dsw.getXAResourceHolderStateGtrids()); } return result; } } /** * {@link JmsPooledConnection} {@link bitronix.tm.resource.common.StateChangeListener}. * When state changes to State.CLOSED, the connection is unregistered from * {@link bitronix.tm.utils.ManagementRegistrar}. */ private final class JmsPooledConnectionStateChangeListener implements StateChangeListener<JmsPooledConnection> { @Override public void stateChanged(JmsPooledConnection source, State oldState, State newState) { if (newState == State.IN_POOL) { if (log.isDebugEnabled()) { log.debug("requeued JMS connection of " + poolingConnectionFactory); } lastReleaseDate = new Date(MonotonicClock.currentTimeMillis()); } if (oldState == State.IN_POOL && newState == State.ACCESSIBLE) { acquisitionDate = new Date(MonotonicClock.currentTimeMillis()); } if (newState == State.CLOSED) { ManagementRegistrar.unregister(jmxName); } } @Override public void stateChanging(JmsPooledConnection source, State currentState, State futureState) { } } /** * {@link JmsConnectionHandle} {@link bitronix.tm.resource.common.StateChangeListener}. * When state changes to State.CLOSED, the session is removed from the list of opened sessions. */ private final class JmsConnectionHandleStateChangeListener implements StateChangeListener<DualSessionWrapper> { @Override public void stateChanged(DualSessionWrapper source, State oldState, State newState) { if (newState == State.CLOSED) { synchronized (sessions) { sessions.remove(source); if (log.isDebugEnabled()) { log.debug("DualSessionWrapper has been closed, " + sessions.size() + " session(s) left open in pooled connection"); } } } } @Override public void stateChanging(DualSessionWrapper source, State currentState, State futureState) { } } public DualSessionWrapper getXAResourceHolderForXaResource(XAResource xaResource) { synchronized (sessions) { for (DualSessionWrapper xaResourceHolder : sessions) { if (xaResourceHolder.getXAResource() == xaResource) { return xaResourceHolder; } } return null; } } }