/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.internal.soa.esb.rosetta.pooling;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.XAConnection;
import javax.jms.XAConnectionFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import org.apache.log4j.Logger;
import org.jboss.soa.esb.addressing.eprs.JMSEpr;
import org.jboss.soa.esb.common.Configuration;
import org.jboss.soa.esb.common.Environment;
import org.jboss.soa.esb.common.ModulePropertyManager;
import org.jboss.soa.esb.common.TransactionStrategy;
import org.jboss.soa.esb.common.TransactionStrategyException;
import org.jboss.soa.esb.helpers.NamingContextException;
import org.jboss.soa.esb.helpers.NamingContextPool;
import org.jboss.soa.esb.util.ClassUtil;
import org.jboss.soa.esb.util.JmsUtil;
import com.arjuna.common.util.propertyservice.PropertyManager;
/**
* JmsConnectionPool.
*
* @author kstam
* @author <a href="mailto:daniel.bevenius@gmail.com">Daniel Bevenius</a>
* Date: March 10, 2007
*/
public class JmsConnectionPool
{
private static final int DEFAULT_POOL_SIZE = 20;
private static final int DEFAULT_SLEEP = 30;
private static int CONFIGURED_POOL_SIZE = DEFAULT_POOL_SIZE;
private static int CONFIGURED_SLEEP = DEFAULT_SLEEP;
static final int XA_TRANSACTED = -1 ;
/**
* The executor used to create sessions.
*/
private static final Executor SESSION_EXECUTOR = Executors.newSingleThreadExecutor(new DaemonThreadFactory()) ;
/**
* The completion service.
*/
private static final CompletionService<JmsSession> COMPLETION_SERVICE = new ExecutorCompletionService<JmsSession>(SESSION_EXECUTOR) ;
/** Logger */
private static final Logger LOGGER = Logger.getLogger(JmsConnectionPool.class);
/**
* The JMS COnnection Exception Handlers.
*/
private static final JmsConnectionExceptionHandler[] CONNECTION_EXCEPTION_HANDLERS ;
/** Maximum number of Sessions that will be created in this pool */
private int maxSessions = DEFAULT_POOL_SIZE; //TODO Make this manageable
/**
* Connection ClientId.
*/
private String clientId;
/**
* The max number of sessions per connection. Defaults to "maxSessions".
*/
private int maxSessionsPerConnection;
/**
* The max number of XA sessions per connection. Defaults to "maxSessionsPerConnection".
*/
private int maxXASessionsPerConnection;
/** Time to sleep when trying to get a session. */
private int sleepTime = DEFAULT_SLEEP;
/** The Indentifier of the pool */
private Map<String, String> poolKey;
/**
* JMS Session Pools.
*/
private List<JmsSessionPool> sessionPools = new ArrayList<JmsSessionPool>();
/**
* The flag representing XA aware connections.
*/
private Boolean isXAAware ;
/**
* Flag signifying that the pool has been terminated.
*/
private boolean terminated ;
/**
* The pool instance id.
*/
private long id ;
/**
* Contructor of the pool.
*
*/
public JmsConnectionPool(Map<String, String> poolKey) throws ConnectionException {
this(poolKey, JmsConnectionPool.CONFIGURED_POOL_SIZE, JmsConnectionPool.CONFIGURED_SLEEP);
}
public JmsConnectionPool(Map<String, String> poolKey, int poolSize, int sleepTime) throws ConnectionException {
this.poolKey = poolKey;
maxSessions = poolSize;
this.sleepTime = sleepTime;
maxSessionsPerConnection = getIntPoolConfig(poolKey, JMSEpr.MAX_SESSIONS_PER_CONNECTION, maxSessions);
if(maxSessionsPerConnection < 1) {
throw new ConnectionException("Invalid '" + JMSEpr.MAX_SESSIONS_PER_CONNECTION + "' configuration value '" + maxSessionsPerConnection + "'. Must be greater than 0.");
}
maxXASessionsPerConnection = getIntPoolConfig(poolKey, JMSEpr.MAX_XA_SESSIONS_PER_CONNECTION, maxSessionsPerConnection);
if(maxXASessionsPerConnection < 1) {
throw new ConnectionException("Invalid '" + JMSEpr.MAX_XA_SESSIONS_PER_CONNECTION + "' configuration value '" + maxXASessionsPerConnection + "'. Must be greater than 0.");
} else if(maxXASessionsPerConnection > maxSessionsPerConnection) {
throw new ConnectionException("Invalid '" + JMSEpr.MAX_XA_SESSIONS_PER_CONNECTION + "' configuration value '" + maxXASessionsPerConnection + "'. Cannot be greater than the configured value for '" + JMSEpr.MAX_SESSIONS_PER_CONNECTION + "', which is " + maxSessionsPerConnection + ".");
}
clientId = poolKey.get(JMSEpr.CLIENT_ID);
}
private int getIntPoolConfig(Map<String, String> poolKey, String configKey, int defaultVal) throws ConnectionException {
String configValueString = poolKey.get(configKey);
int configValue = defaultVal;
if(configValueString != null) {
try {
configValue = Integer.parseInt(configValueString.trim());
} catch(NumberFormatException e) {
throw new ConnectionException("Invalid '" + configKey + "' configuration value '" + configValueString.trim() + "'. Must be a valid Integer.");
}
}
return configValue;
}
protected int getMaxSessions() {
return maxSessions;
}
protected int getMaxSessionsPerConnection() {
return maxSessionsPerConnection;
}
public int getMaxXASessionsPerConnection() {
return maxXASessionsPerConnection;
}
protected List<JmsSessionPool> getSessionPools() {
return sessionPools;
}
/**
* This method can be called whenever a connection is needed from the pool.
*
* @return Connection to be used
* @throws ConnectionException
*/
public synchronized JmsSession getSession(final int acknowledgeMode) throws NamingException, JMSException, ConnectionException
{
if (terminated)
{
throw new ConnectionException("Connection pool has been terminated") ;
}
try
{
return internalGetSession(acknowledgeMode) ;
}
catch (final JMSException jmse)
{
if (isConnectionFailure(jmse) != null)
{
return internalGetSession(acknowledgeMode) ;
}
throw jmse ;
}
}
private synchronized JmsSession internalGetSession(final int acknowledgeMode)
throws NamingException, JMSException, ConnectionException
{
if(sessionPools.isEmpty()) {
// Create the first pool entry...
addSessionPool();
}
if(isXAAware == null) {
try
{
getFactoryConnection() ;
}
catch (final NamingContextException nce)
{
throw new ConnectionException("Unexpected exception accessing Naming Context", nce) ;
}
}
final boolean transacted ;
try {
transacted = (isXAAware && TransactionStrategy.getTransactionStrategy(true).isActive()) ;
} catch (final TransactionStrategyException tse) {
throw new ConnectionException("Failed to determine current transaction context", tse) ;
}
if (transacted)
{
final JmsXASession currentSession = getXASession() ;
if (currentSession != null)
{
currentSession.incrementReferenceCount() ;
return currentSession ;
}
}
final int mode = (transacted ? XA_TRANSACTED : acknowledgeMode) ;
final long end = System.currentTimeMillis() + (sleepTime * 1000) ;
boolean emitExpiry = LOGGER.isDebugEnabled() ;
while(true) {
// Cycle through all the existing session pools and try getting a
// free session. Will JmsSessionPool.getSession will add a session
// to a pool that is not "full"...
for (JmsSessionPool sessionPool : sessionPools) {
try {
JmsSession session = sessionPool.getSession(mode, transacted);
if(session != null) {
return session;
}
} catch (final JMSException jmse) {
final Boolean failure = isConnectionFailure(jmse) ;
if (failure != null) {
sessionPool.cleanSessionPool(failure.booleanValue()) ;
}
throw jmse ;
}
}
// OK... all the existing session pools are full and have no free sessions. If we can add
// another session pool, add it and then start this loop again...
if(getSessionsInPool() < maxSessions) {
addSessionPool();
continue;
}
// We've reached our max permitted number of connections and the sessions
// associated with these connections are all in use. Drop into the
// delay below and wait for a session to be freed...
if (emitExpiry)
{
LOGGER.debug("The connection pool was exhausted, waiting for a session to be released.") ;
emitExpiry = false ;
}
// Wait and loop again...
final long now = System.currentTimeMillis() ;
final long delay = (end - now) ;
if (delay <= 0)
{
throw new ConnectionException("Could not obtain a JMS connection from the pool after "+ sleepTime +"s.");
}
else
{
try
{
wait(delay) ;
}
catch (final InterruptedException ie) {} // ignore
}
}
}
private Object getFactoryConnection()
throws NamingContextException, NamingException
{
String connectionFactoryString = poolKey.get(JMSEpr.CONNECTION_FACTORY_TAG);
Object factoryConnection=null;
Properties jndiEnvironment = JmsConnectionPoolContainer.getJndiEnvironment(poolKey);
Context jndiContext = NamingContextPool.getNamingContext(jndiEnvironment);
try
{
factoryConnection = jndiContext.lookup(connectionFactoryString);
}
catch (NamingException ne)
{
LOGGER.info("Received NamingException, refreshing context.");
jndiContext = NamingContextPool.replaceNamingContext(jndiContext, JmsConnectionPoolContainer.getJndiEnvironment(poolKey));
factoryConnection = jndiContext.lookup(connectionFactoryString);
}
finally
{
NamingContextPool.releaseNamingContext(jndiContext) ;
}
isXAAware = (factoryConnection instanceof XAConnectionFactory) ;
return factoryConnection ;
}
private JmsSessionPool addSessionPool() throws NamingException, JMSException, ConnectionException {
final JmsSessionPool sessionPool = new JmsSessionPool();
// And add it to the pool...
sessionPools.add(sessionPool);
return sessionPool;
}
/**
* This method can be called whenever a Session is needed from the pool.
* @return
* @throws NamingException
* @throws JMSException
* @throws ConnectionException
*/
public JmsSession getSession() throws NamingException, JMSException, ConnectionException
{
return getSession(Session.AUTO_ACKNOWLEDGE);
}
/**
* This method closes an open connection and returns the connection to the pool.
* @param session The connection to be returned to the pool.
* @throws SQLException
* @deprecated
*/
public void closeSession(Session session){
if (session instanceof JmsSession) {
closeSession((JmsSession)session) ;
} else {
LOGGER.error("Invalid JMS Session type in closeSession: " + session);
}
}
/**
* This method closes an open connection and returns the connection to the pool.
* @param session The connection to be returned to the pool.
* @throws SQLException
*/
public void closeSession(JmsSession session){
session.handleCloseSession(this) ;
}
/**
* Handle the real work of closing the connection.
* @param session The session to close.
*/
synchronized void handleCloseSession(final JmsSession session)
{
JmsSessionPool sessionPool = findOwnerPool(session);
if(sessionPool != null) {
sessionPool.handleCloseSession(session);
}
}
/**
* Handle the real work of releasing the connection.
* @param session The session to release.
*/
synchronized void handleReleaseSession(final JmsSession session)
{
session.releaseResources();
try
{
session.close() ;
}
catch (final Throwable th) {} // ignore
releaseInUseSession(session) ;
}
/**
* Handle exceptions that occur in the session or its child objects.
* @param jmsSession The jmsSession associated with the call.
* @param jmse The JMSException to check.
* @throws JMSException if the exception is to be overridden.
*/
void handleException(final JmsSession jmsSession, final JMSException jmse)
throws JMSException
{
final Boolean failure = isConnectionFailure(jmse) ;
if (failure != null) {
final JmsSessionPool sessionPool = findOwnerPool(jmsSession);
if(sessionPool != null) {
sessionPool.cleanSessionPool(failure.booleanValue()) ;
}
throw new JmsConnectionFailureException("The underlying exception appears to have failed", jmse) ;
}
}
/**
* Check the exception to see whether it indicates a connection failure
* @param jmse The current JMS Exception
* @return null if no connection failure, TRUE if the connection should be closed, FALSE if just cleaned up.
*/
private Boolean isConnectionFailure(final JMSException jmse)
{
for(JmsConnectionExceptionHandler connectionExceptionHandler: CONNECTION_EXCEPTION_HANDLERS)
{
final Boolean isConnectionFailure = connectionExceptionHandler.isConnectionFailure(jmse) ;
if (isConnectionFailure != null)
{
LOGGER.debug("Connection failure detected by handler: " + connectionExceptionHandler.getClass().getName(), jmse) ;
LOGGER.debug("Linked exception", jmse.getLinkedException()) ;
return isConnectionFailure ;
}
}
return null ;
}
/**
* Release a session from the in use mapping.
* @param session The session to release.
*/
private void releaseInUseSession(final JmsSession session)
{
JmsSessionPool sessionPool = findOwnerPool(session);
if(sessionPool != null) {
sessionPool.releaseInUseSession(session);
}
notifyAll() ;
}
/**
* This method closes an open session without returning it to the pool.
* @param session The session to be returned to the pool.
* @throws SQLException
*/
public synchronized void releaseSession(final JmsSession session) {
session.handleReleaseSession(this) ;
}
/**
* This method closes an open session without returning it to the pool.
* @param session The session to be returned to the pool.
* @throws SQLException
* @deprecated
*/
public synchronized void releaseSession(final Session session) {
if (session instanceof JmsSession) {
releaseSession((JmsSession)session) ;
} else {
LOGGER.error("Invalid JMS Session type in releaseSession: " + session);
}
}
/**
* This method is called when the pool needs to destroyed. It closes all open sessions
* and the connection and removes it from the container's poolMap.
*/
public synchronized void removeSessionPool()
{
for(JmsSessionPool sessionPool : sessionPools) {
try {
sessionPool.removeSessionPool();
} catch(Exception e) {
LOGGER.error("Exception while removing JmsSessionPool.", e);
}
}
sessionPools.clear();
terminated = true ;
JmsConnectionPoolContainer.removePool(poolKey);
}
/**
* Gets the total number of sessions in the pool regardless of the acknowlede mode
* used when creating the sessions.
* @return the session pool size
*/
public int getSessionsInPool() {
return getSessionsInPool(Session.AUTO_ACKNOWLEDGE) +
getSessionsInPool(Session.CLIENT_ACKNOWLEDGE) +
getSessionsInPool(Session.DUPS_OK_ACKNOWLEDGE) +
getSessionsInPool(Session.SESSION_TRANSACTED) +
getSessionsInPool(XA_TRANSACTED) ;
}
/**
* Returns the total nr of sessions for the specifed acknowledge mode
*
* @param acknowledgeMode the acknowledge mode of sessions
* @return
*/
public synchronized int getSessionsInPool(final int acknowledgeMode) {
return getFreeSessionsInPool(acknowledgeMode) + getInUseSessionsInPool(acknowledgeMode) ;
}
/**
* Get the number of free sessions created with the specified acknowledge mode
* @param acknowledgeMode the acknowledge mode of sessions
* @return int the number of in use sessions
*/
public synchronized int getFreeSessionsInPool(final int acknowledgeMode) {
int count = 0;
for(JmsSessionPool sessionPool : sessionPools) {
count += sessionPool.getFreeSessionsInPool(acknowledgeMode);
}
return count;
}
/**
* Get the number of sessions that are in use and that were
* created with the specified acknowledge mode
* @param acknowledgeMode the acknowledge mode of sessions
* @return int the number of in use sessions
*/
public synchronized int getInUseSessionsInPool(final int acknowledgeMode) {
int count = 0;
for(JmsSessionPool sessionPool : sessionPools) {
count += sessionPool.getInUseSessionsInPool(acknowledgeMode);
}
return count;
}
/**
* Get the current transaction.
* @return The transaction or null if none present.
* @throws ConnectionException if the transaction context cannot be obtained.
*/
private Object getTransaction()
throws ConnectionException
{
try {
return TransactionStrategy.getTransactionStrategy(true).getTransaction() ;
} catch (final TransactionStrategyException tse) {
throw new ConnectionException("Failed to determine current transaction context", tse) ;
}
}
/**
* Get a JMS session associated with the current transaction.
* @return The JMS session or null if not associated.
* @throws ConnectionException For accessint the current transaction context
*/
private synchronized JmsXASession getXASession()
throws ConnectionException
{
final Object tx = getTransaction() ;
for(JmsSessionPool sessionPool : sessionPools) {
JmsXASession session = sessionPool.transactionsToSessions.get(tx);
if(session != null) {
return session;
}
}
return null;
}
/**
* Associate the JMS XA Session with the current transaction.
* @param session The XA session.
* @throws ConnectionException if there is no transaction active.
*/
void associateTransaction(final JmsXASession session)
throws ConnectionException
{
JmsSessionPool sessionPool = findOwnerPool(session);
if(sessionPool != null) {
sessionPool.associateTransaction(session);
}
}
/**
* Disassociate the JMS XA Session from a transaction.
* @param session The XA session.
*/
void disassociateTransaction(final JmsXASession session)
{
JmsSessionPool sessionPool = findOwnerPool(session);
if(sessionPool != null) {
sessionPool.disassociateTransaction(session);
}
}
/**
* Check that the session is associated with the current transaction.
* @param session The XA session.
*/
boolean isAssociated(final JmsXASession session)
{
JmsSessionPool sessionPool = findOwnerPool(session);
if(sessionPool != null) {
return sessionPool.isAssociated(session);
} else {
return false ;
}
}
JmsSessionPool findOwnerPool(final JmsSession session) {
return session.getSessionPool() ;
}
static
{
PropertyManager prop = ModulePropertyManager.getPropertyManager(ModulePropertyManager.TRANSPORTS_MODULE);
String value = prop.getProperty(Environment.JMS_CONNECTION_POOL_SIZE);
if (value != null)
{
try
{
CONFIGURED_POOL_SIZE = Integer.parseInt(value);
}
catch (NumberFormatException ex)
{
ex.printStackTrace();
}
}
value = prop.getProperty(Environment.JMS_SESSION_SLEEP);
if (value != null)
{
try
{
CONFIGURED_SLEEP = Integer.parseInt(value);
}
catch (NumberFormatException ex)
{
ex.printStackTrace();
}
}
value = Configuration.getJMSConnectionExceptionHandlers() ;
final String[] handlerNames = (value == null ? null : value.split(",")) ;
final ArrayList<JmsConnectionExceptionHandler> handlers = new ArrayList<JmsConnectionExceptionHandler>() ;
for(String handler: handlerNames)
{
final Class<?> handlerClass ;
try
{
handlerClass = ClassUtil.forName(handler.trim(), JmsConnectionPool.class) ;
}
catch (final ClassNotFoundException cnfe)
{
LOGGER.error("Failed to instantiate connection exception handler, ignoring", cnfe) ;
continue ;
}
if (!JmsConnectionExceptionHandler.class.isAssignableFrom(handlerClass))
{
LOGGER.error("Handler does not implement JmsConnectionExceptionHandler interface, ignoring: " + handlerClass.getName()) ;
continue ;
}
final Object instance ;
try
{
instance = handlerClass.newInstance() ;
}
catch (final Throwable th)
{
LOGGER.error("Failed to instantiate handler, ignoring", th) ;
continue ;
}
handlers.add((JmsConnectionExceptionHandler)instance) ;
}
CONNECTION_EXCEPTION_HANDLERS = handlers.toArray(new JmsConnectionExceptionHandler[handlers.size()]) ;
}
class JmsSessionPool {
/** Reference to a Queue or Topic Connection, we only need one per pool */
protected Connection jmsConnection ;
/** Number of free sessions in the pool that can be given out. Indexed by session key */
private Map<Integer,ArrayList<JmsSession>> freeSessionsMap = new HashMap<Integer,ArrayList<JmsSession>>();
/** Number of session that are currently in use. Indexed by session key mode */
private Map<Integer,ArrayList<JmsSession>> inUseSessionsMap = new HashMap<Integer,ArrayList<JmsSession>>();
/**
* Mapping from transactions to sessions.
*/
private Map<Object, JmsXASession> transactionsToSessions = new HashMap<Object, JmsXASession>() ;
/**
* Mapping from sessions to transactions.
*/
private Map<JmsXASession, Object> sessionsToTransactions = new HashMap<JmsXASession, Object>() ;
private JmsSessionPool() {
freeSessionsMap.put(Session.AUTO_ACKNOWLEDGE, new ArrayList<JmsSession>() );
freeSessionsMap.put(Session.CLIENT_ACKNOWLEDGE, new ArrayList<JmsSession>() );
freeSessionsMap.put(Session.DUPS_OK_ACKNOWLEDGE, new ArrayList<JmsSession>() );
freeSessionsMap.put(Session.SESSION_TRANSACTED, new ArrayList<JmsSession>() );
inUseSessionsMap.put(Session.AUTO_ACKNOWLEDGE, new ArrayList<JmsSession>() );
inUseSessionsMap.put(Session.CLIENT_ACKNOWLEDGE, new ArrayList<JmsSession>() );
inUseSessionsMap.put(Session.DUPS_OK_ACKNOWLEDGE, new ArrayList<JmsSession>() );
inUseSessionsMap.put(Session.SESSION_TRANSACTED, new ArrayList<JmsSession>() );
}
public synchronized void removeSessionPool() {
freeSessionsMap = null ;
inUseSessionsMap = null ;
transactionsToSessions = null ;
sessionsToTransactions = null ;
LOGGER.debug("Emptied the session pool now closing the connection to the factory.");
if (jmsConnection!=null) {
try {
jmsConnection.close();
} catch (final Exception ex) {} // ignore
jmsConnection=null;
}
}
public synchronized JmsSession getSession(int acknowledgeMode, boolean transacted) throws ConnectionException, NamingException, JMSException {
initConnection() ;
ArrayList<JmsSession> freeSessions = freeSessionsMap.get(acknowledgeMode );
ArrayList<JmsSession> inUseSessions = inUseSessionsMap.get(acknowledgeMode);
if (freeSessions.size() > 0)
{
final JmsSession session = freeSessions.remove(freeSessions.size()-1);
if (transacted)
{
final JmsXASession xaSession = (JmsXASession)session ;
xaSession.incrementReferenceCount() ;
}
inUseSessions.add(session);
return session ;
} else if (getSessionsInPool() < maxSessionsPerConnection) {
JmsSession session = null;
if(transacted) {
if(getXASessionsInPool() < maxXASessionsPerConnection) {
session = addAnotherSession(poolKey, transacted, acknowledgeMode);
}
} else {
session = addAnotherSession(poolKey, transacted, acknowledgeMode);
}
if(session != null) {
inUseSessions.add(session);
return session ;
}
}
return null;
}
private synchronized void initConnection()
throws ConnectionException, NamingException, JMSException
{
if (terminated)
{
throw new ConnectionException("Connection pool has been terminated") ;
}
if (jmsConnection == null)
{
try {
LOGGER.debug("Creating a JMS Connection for poolKey : " + poolKey);
final Object factoryConnection = getFactoryConnection() ;
final String username = poolKey.get( JMSEpr.JMS_SECURITY_PRINCIPAL_TAG );
String password = poolKey.get( JMSEpr.JMS_SECURITY_CREDENTIAL_TAG );
boolean useJMSSecurity = JmsUtil.isSecurityConfigured(username, password);
LOGGER.debug( "JMS Security principal [" + username + "] using JMS Security : " + useJMSSecurity );
if (useJMSSecurity)
{
password = JmsUtil.getPasswordFromFile(password);
}
if (isXAAware)
{
final XAConnectionFactory factory = (XAConnectionFactory)factoryConnection ;
jmsConnection = useJMSSecurity ? factory.createXAConnection(username,password): factory.createXAConnection();
freeSessionsMap.put(XA_TRANSACTED, new ArrayList<JmsSession>() );
inUseSessionsMap.put(XA_TRANSACTED, new ArrayList<JmsSession>() );
}
else if (factoryConnection instanceof ConnectionFactory)
{
final ConnectionFactory factory = (ConnectionFactory)factoryConnection ;
jmsConnection = useJMSSecurity ? factory.createConnection(username,password): factory.createConnection();
}
else
{
throw new ConnectionException("Unknown factory connection type: " + factoryConnection.getClass().getCanonicalName());
}
if(clientId != null) {
jmsConnection.setClientID(clientId);
}
jmsConnection.setExceptionListener(new ExceptionListener() {
public void onException(JMSException arg0)
{
// This will result in all connections (and their sessions) on this pool
// being closed...
cleanSessionPool() ;
}
}) ;
jmsConnection.start();
} catch (final NamingContextException nce) {
throw new ConnectionException("Unexpected exception accessing Naming Context", nce) ;
}
}
}
/**
* This is where we create the sessions.
*
* @param poolKey
* @param transacted
* @throws JMSException
*/
private synchronized JmsSession addAnotherSession(Map<String, String> poolKey, final boolean transacted, final int acknowledgeMode)
throws JMSException
{
// Sessions need to be created in this way because of an issue with JBM.
// See https://jira.jboss.org/jira/browse/JBESB-1799
final long currentID = id ;
final Connection currentConnection = jmsConnection ;
final Future<JmsSession> future = COMPLETION_SERVICE.submit(new Callable<JmsSession>() {
public JmsSession call()
throws JMSException
{
final JmsSession session ;
if (transacted) {
final JmsXASession xaSession = new JmsXASession(JmsConnectionPool.this, JmsSessionPool.this, ((XAConnection)currentConnection).createXASession(), currentID, acknowledgeMode);
xaSession.incrementReferenceCount() ;
session = xaSession ;
} else {
session = new JmsSession(JmsConnectionPool.this, JmsSessionPool.this, currentConnection.createSession(transacted, acknowledgeMode), currentID, acknowledgeMode);
}
return session ;
}
}) ;
// For now we only support JTA transacted sessions
try
{
JmsSession session = future.get();
LOGGER.debug("Number of Sessions in the pool with acknowledgeMode: " + acknowledgeMode + " is now " + getSessionsInPool(acknowledgeMode));
return session;
}
catch (final InterruptedException ie) {} // ignore
catch (final ExecutionException ee)
{
final Throwable th = ee.getCause() ;
if (th instanceof JMSException)
{
throw (JMSException)th ;
}
if (th instanceof Error)
{
throw (Error)th ;
}
if (th instanceof RuntimeException)
{
throw (RuntimeException)th ;
}
}
return null;
}
public void cleanSessionPool() {
cleanSessionPool(true) ;
}
public void cleanSessionPool(final boolean closeConnection) {
final Connection connection ;
synchronized(this)
{
if (terminated)
{
return ;
}
id++ ;
for (List<JmsSession> list : freeSessionsMap.values())
{
list.clear() ;
}
for (List<JmsSession> list : inUseSessionsMap.values())
{
list.clear() ;
}
transactionsToSessions.clear() ;
sessionsToTransactions.clear() ;
LOGGER.debug("Cleared the session pool now closing the connection to the factory.");
connection = jmsConnection ;
jmsConnection = null ;
}
if (closeConnection && (connection!=null)) {
try {
connection.close();
} catch (final Exception ex) {} // ignore
}
}
/**
* Returns the total number of sessions in the pool.
* @return The total number of sessions in the pool.
*/
private synchronized int getSessionsInPool() {
// Get a count of all sessions (of any type) in the pool...
return getSessionsInPool(JmsSession.class);
}
/**
* Returns the total number of XA sessions in the pool.
* @return The total number of XA sessions in the pool.
*/
private synchronized int getXASessionsInPool() {
// Get a count of XA sessions in the pool...
return getSessionsInPool(JmsXASession.class);
}
/**
* Returns the total number of XA sessions in the pool.
* @return The total number of XA sessions in the pool.
*/
private synchronized int getSessionsInPool(Class<? extends JmsSession> jmsSessionType) {
int total = 0;
total += getSessionsInMap(freeSessionsMap, jmsSessionType);
total += getSessionsInMap(inUseSessionsMap, jmsSessionType);
return total;
}
private synchronized int getSessionsInMap(Map<Integer, ArrayList<JmsSession>> sessionsMap, Class<? extends JmsSession> jmsSessionType) {
Collection<ArrayList<JmsSession>> sessionLists = sessionsMap.values();
int total = 0;
for(ArrayList<JmsSession> sessionList : sessionLists) {
for(JmsSession session : sessionList) {
if(jmsSessionType.isAssignableFrom(session.getClass())) {
total++;
}
}
}
return total;
}
/**
* Returns the total nr of sessions for the specifed acknowledge mode
*
* @param acknowledgeMode the acknowledge mode of sessions
* @return
*/
public synchronized int getSessionsInPool(final int acknowledgeMode) {
return getFreeSessionsInPool(acknowledgeMode) + getInUseSessionsInPool(acknowledgeMode) ;
}
/**
* Get the number of free sessions created with the specified acknowledge mode
* @param acknowledgeMode the acknowledge mode of sessions
* @return int the number of in use sessions
*/
public synchronized int getFreeSessionsInPool(final int acknowledgeMode) {
final ArrayList<JmsSession> freeSessionMap = (freeSessionsMap == null ? null : freeSessionsMap.get(acknowledgeMode)) ;
final int numFreeSessions = (freeSessionMap == null ? 0 : freeSessionMap.size()) ;
return numFreeSessions;
}
/**
* Get the number of sessions that are in use and that were
* created with the specified acknowledge mode
* @param acknowledgeMode the acknowledge mode of sessions
* @return int the number of in use sessions
*/
public synchronized int getInUseSessionsInPool(final int acknowledgeMode) {
final ArrayList<JmsSession> inUseSessionMap = (inUseSessionsMap == null ? null : inUseSessionsMap.get(acknowledgeMode)) ;
final int numInUseSessions = (inUseSessionMap == null ? 0 : inUseSessionMap.size()) ;
return numInUseSessions;
}
/**
* Associate the JMS XA Session with the current transaction.
* @param session The XA session.
* @throws ConnectionException if there is no transaction active.
*/
void associateTransaction(final JmsXASession session)
throws ConnectionException
{
final Object tx = getTransaction() ;
if (tx == null)
{
throw new ConnectionException("No active transaction") ;
}
synchronized (this)
{
transactionsToSessions.put(tx, session) ;
sessionsToTransactions.put(session, tx) ;
}
}
/**
* Disassociate the JMS XA Session from a transaction.
* @param session The XA session.
*/
void disassociateTransaction(final JmsXASession session)
{
final Object tx ;
synchronized(this)
{
tx = sessionsToTransactions.remove(session) ;
transactionsToSessions.remove(tx) ;
}
}
/**
* Check that the session is associated with the current transaction.
* @param session The XA session.
*/
boolean isAssociated(final JmsXASession session)
{
try
{
final Object current = TransactionStrategy.getTransactionStrategy(true).getTransaction() ;
final Object tx ;
synchronized(this)
{
tx = sessionsToTransactions.get(session) ;
}
if (tx != null)
{
return tx.equals(current) ;
}
}
catch (final TransactionStrategyException tse) {} // ignore
return false ;
}
public void releaseInUseSession(JmsSession session) {
final int mode = session.getRequestedAcknowledgeMode() ;
final ArrayList<JmsSession> sessions = (inUseSessionsMap == null ? null : inUseSessionsMap.get(mode));
if (sessions != null) {
sessions.remove(session) ;
}
}
public void handleCloseSession(JmsSession session) {
if (session.isSuspect())
{
LOGGER.debug("Session is suspect, dropping") ;
handleReleaseSession(session) ;
}
else
{
if (session.getId() != id)
{
LOGGER.debug("Session is from a previous incarnation, dropping") ;
}
else
{
final int mode = session.getRequestedAcknowledgeMode() ;
final ArrayList<JmsSession> sessions = (freeSessionsMap == null ? null : freeSessionsMap.get(mode));
if (sessions != null) {
sessions.add(session.duplicateSession()) ;
}
}
session.releaseResources() ;
releaseInUseSession(session) ;
}
}
}
/**
* Thread factory returning daemon threads.
* <p/>
* Required as part of the fix for https://jira.jboss.org/jira/browse/JBESB-1799
*
* @author kevin
*/
private static final class DaemonThreadFactory implements ThreadFactory
{
/**
* The default executor factory.
*/
private final ThreadFactory defaultFactory = Executors.defaultThreadFactory() ;
/**
* Return a new daemon thread.
* @param runnable The runnable associated with the thread.
*/
public Thread newThread(final Runnable runnable)
{
final Thread thread = defaultFactory.newThread(runnable) ;
thread.setDaemon(true) ;
return thread ;
}
}
}