/* * JBoss, Home of Professional Open Source. * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file 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.resource.connectionmanager; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Iterator; import java.util.Map; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.resource.ResourceException; import javax.resource.spi.ConnectionRequestInfo; import javax.resource.spi.ManagedConnectionFactory; import javax.security.auth.Subject; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.jboss.deployment.DeploymentException; import org.jboss.logging.Logger; import org.jboss.mx.util.JMXExceptionDecoder; import org.jboss.resource.JBossResourceException; import org.jboss.resource.connectionmanager.InternalManagedConnectionPool.PoolParams; import org.jboss.system.ServiceMBeanSupport; import org.jboss.tm.TransactionLocal; import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap; /** * The JBossManagedConnectionPool mbean configures and supplies pooling of * JBossConnectionEventListeners to the BaseConnectionManager2 mbean.

* * It may be replaced by any mbean with a readable ManagedConnectionPool attribute * of type ManagedConnectionPool. Normal pooling parameters are supplied, * and the criteria to distinguish ManagedConnections is set in the Criteria attribute. * * @author David Jencks * @author Adrian Brock * @author Weston Price * * @version $Revision: 59880 $ */ public class JBossManagedConnectionPool extends ServiceMBeanSupport implements JBossManagedConnectionPoolMBean, NotificationListener { /** The log */ private static final Logger log = Logger.getLogger(JBossManagedConnectionPool.class); /** The managed connection factory name */ private ObjectName managedConnectionFactoryName; /** The pooling criteria */ private String criteria; /** The pooling strategy */ private ManagedConnectionPool poolingStrategy; /** The pooling parameters */ private final InternalManagedConnectionPool.PoolParams poolParams = new InternalManagedConnectionPool.PoolParams(); /** Whether to use separate pools for transactional and non-transaction use */ private boolean noTxSeparatePools; private String poolJndiName; public JBossManagedConnectionPool() { } public ManagedConnectionPool getManagedConnectionPool() { return poolingStrategy; } public ObjectName getManagedConnectionFactoryName() { return managedConnectionFactoryName; } public void setManagedConnectionFactoryName(ObjectName newManagedConnectionFactoryName) { this.managedConnectionFactoryName = newManagedConnectionFactoryName; } public long getAvailableConnectionCount() { return (poolingStrategy == null) ? 0 : poolingStrategy.getAvailableConnectionCount(); } public long getMaxConnectionsInUseCount() { return (poolingStrategy == null) ? 0 : poolingStrategy.getMaxConnectionsInUseCount(); } public long getInUseConnectionCount() { return (poolingStrategy == null) ? 0 : poolingStrategy.getInUseConnectionCount(); } public int getMinSize() { return poolParams.minSize; } public void setMinSize(int newMinSize) { poolParams.minSize = newMinSize; } public int getMaxSize() { return poolParams.maxSize; } public void setMaxSize(int newMaxSize) { poolParams.maxSize = newMaxSize; } public int getBlockingTimeoutMillis() { return poolParams.blockingTimeout; } public void setBlockingTimeoutMillis(int newBlockingTimeout) { poolParams.blockingTimeout = newBlockingTimeout; } public long getIdleTimeoutMinutes() { return poolParams.idleTimeout / (1000 * 60); } public void setIdleTimeoutMinutes(long newIdleTimeoutMinutes) { poolParams.idleTimeout = newIdleTimeoutMinutes * 1000 * 60; } /** * Get the IdleTimeout value. * * @return the IdleTimeout value. */ public long getIdleTimeout() { return poolParams.idleTimeout; } /** * Set the IdleTimeout value. * * @param newIdleTimeout The new IdleTimeout value. */ public void setIdleTimeout(long newIdleTimeout) { poolParams.idleTimeout = newIdleTimeout; } public String getCriteria() { return criteria; } public void setCriteria(String newCriteria) { this.criteria = newCriteria; } public boolean getNoTxSeparatePools() { return noTxSeparatePools; } public void setNoTxSeparatePools(boolean value) { this.noTxSeparatePools = value; } public boolean getBackGroundValidation() { return poolParams.backgroundValidation; } public void setBackGroundValidation(boolean backgroundValidation) { poolParams.backgroundValidation = backgroundValidation; } public long getBackGroundValidationMinutes() { return poolParams.backgroundInterval / (1000 * 60); } public void setBackGroundValidationMinutes(long backgroundValidationInterval) { poolParams.backgroundInterval = backgroundValidationInterval * 1000 * 60; } public boolean getPreFill() { return this.poolParams.prefill; } public void setPreFill(boolean prefill) { this.poolParams.prefill = prefill; } public boolean getUseFastFail() { return this.poolParams.useFastFail; } public void setUseFastFail(boolean useFastFail) { this.poolParams.useFastFail = useFastFail; } public void flush() { if (poolingStrategy == null) throw new IllegalStateException("The connection pool is not started"); poolingStrategy.flush(); if (poolingStrategy instanceof PreFillPoolSupport) { final PreFillPoolSupport pfs = (PreFillPoolSupport) poolingStrategy; if (pfs.shouldPreFill()) pfs.prefill(noTxSeparatePools); } } public int getConnectionCount() { return (poolingStrategy == null) ? 0 : poolingStrategy.getConnectionCount(); } public int getConnectionCreatedCount() { return (poolingStrategy == null) ? 0 : poolingStrategy.getConnectionCreatedCount(); } public int getConnectionDestroyedCount() { return (poolingStrategy == null) ? 0 : poolingStrategy.getConnectionDestroyedCount(); } public String getName() { return "JBossManagedConnectionPool"; } public String getPoolJndiName() { return this.poolJndiName; } public void setPoolJndiName(String poolName) { this.poolJndiName = poolName; } protected void startService() throws Exception { ManagedConnectionFactory mcf = null; if (managedConnectionFactoryName == null) throw new DeploymentException("ManagedConnectionFactory not set!"); try { //We are getting the actual mcf instance itself. This will require //some work if the mcf is an xmbean of itself. mcf = (ManagedConnectionFactory) server.getAttribute(managedConnectionFactoryName, "McfInstance"); } catch (Exception e) { JMXExceptionDecoder.rethrow(e); } getServer().addNotificationListener(managedConnectionFactoryName, this, new NotificationFilter() { private static final long serialVersionUID = -9211456539783257343L; public boolean isNotificationEnabled(Notification n) { return RARDeployment.MCF_ATTRIBUTE_CHANGED_NOTIFICATION.equals(n.getType()) && managedConnectionFactoryName.equals(n.getSource()); } }, null); if ("ByContainerAndApplication".equals(criteria)) poolingStrategy = new PoolBySubjectAndCri(mcf, poolJndiName, poolParams, noTxSeparatePools, log); else if ("ByContainer".equals(criteria)) poolingStrategy = new PoolBySubject(mcf, poolJndiName, poolParams, noTxSeparatePools, log); else if ("ByApplication".equals(criteria)) poolingStrategy = new PoolByCri(mcf, poolJndiName, poolParams, noTxSeparatePools, log); else if ("ByNothing".equals(criteria)) poolingStrategy = new OnePool(mcf, poolJndiName, poolParams, noTxSeparatePools, log); else throw new DeploymentException("Unknown pooling criteria: " + criteria); } protected void stopService() throws Exception { if (poolingStrategy != null) poolingStrategy.shutdown(); getServer().removeNotificationListener(managedConnectionFactoryName, this); poolingStrategy = null; } public void handleNotification(Notification notification, Object handback) { flush(); } public static class SubPoolContext { /** The subpool */ private InternalManagedConnectionPool subPool; /** The track by transaction transaction local */ private TransactionLocal trackByTx; /** * Create a new SubPoolContext. * * @param tm the transaction manager * @param mcf the managed connection factory * @param clf the connection listener factory * @param subject the subject * @param cri the connection request info * @param poolParams the pool parameters * @param log the log */ public SubPoolContext(TransactionManager tm, ManagedConnectionFactory mcf, ConnectionListenerFactory clf, Subject subject, ConnectionRequestInfo cri, PoolParams poolParams, Logger log) { subPool = new InternalManagedConnectionPool(mcf, clf, subject, cri, poolParams, log); if (tm != null) trackByTx = new TransactionLocal(tm); } /** * Get the sub pool * * @return the sub pool */ public InternalManagedConnectionPool getSubPool() { return subPool; } /** * Get the track by transaction * * @return the transaction local */ public TransactionLocal getTrackByTx() { return trackByTx; } /** * Initialize the subpool context */ public void initialize() { subPool.initialize(); } } /** * The base pool implementation */ public abstract static class BasePool implements ManagedConnectionPool, PreFillPoolSupport { /** The subpools */ private final Map subPools = new ConcurrentReaderHashMap(); /** The managed connection factory */ private final ManagedConnectionFactory mcf; /** The connection listener factory */ private ConnectionListenerFactory clf; /** The pool parameters */ protected final InternalManagedConnectionPool.PoolParams poolParams; /** Whether to use separate pools for transactional and non-transaction use */ protected boolean noTxSeparatePools; /** The logger */ private final Logger log; /** Is trace enabled */ private boolean traceEnabled = false; /** The poolName */ private String poolName; public BasePool(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this(mcf, null, poolParams, noTxSeparatePools, log); } /** * Create a new base pool * * @param mcf the managed connection factory * @param poolParams the pooling parameters * @param log the log */ public BasePool(final ManagedConnectionFactory mcf, final String poolName, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this.mcf = mcf; this.poolParams = poolParams; this.noTxSeparatePools = noTxSeparatePools; this.log = log; this.traceEnabled = log.isTraceEnabled(); this.poolName = poolName; } /** * Retrieve the key for this request * * @param subject the subject * @param cri the connection request information * @return the key * @throws ResourceException for any error */ protected abstract Object getKey(Subject subject, ConnectionRequestInfo cri, boolean separateNoTx) throws ResourceException; public ManagedConnectionFactory getManagedConnectionFactory() { return mcf; } public void setConnectionListenerFactory(ConnectionListenerFactory clf) { this.clf = clf; } public ConnectionListener getConnection(Transaction trackByTransaction, Subject subject, ConnectionRequestInfo cri) throws ResourceException { // Determine the pool key for this request boolean separateNoTx = false; if (noTxSeparatePools) separateNoTx = clf.isTransactional(); Object key = getKey(subject, cri, separateNoTx); SubPoolContext subPool = getSubPool(key, subject, cri); InternalManagedConnectionPool mcp = subPool.getSubPool(); // Are we doing track by connection? TransactionLocal trackByTx = subPool.getTrackByTx(); // Simple case if (trackByTransaction == null || trackByTx == null) { ConnectionListener cl = mcp.getConnection(subject, cri); if (traceEnabled) dump("Got connection from pool " + cl); return cl; } // Track by transaction try { trackByTx.lock(trackByTransaction); } catch (Throwable t) { JBossResourceException.rethrowAsResourceException("Unable to get connection from the pool for tx=" + trackByTransaction, t); } try { // Already got one ConnectionListener cl = (ConnectionListener) trackByTx.get(trackByTransaction); if (cl != null) { if (traceEnabled) dump("Previous connection tracked by transaction " + cl + " tx=" + trackByTransaction); return cl; } } finally { trackByTx.unlock(trackByTransaction); } // Need a new one for this transaction // This must be done outside the tx local lock, otherwise // the tx timeout won't work and get connection can do a lot of other work // with many opportunities for deadlocks. // Instead we do a double check after we got the transaction to see // whether another thread beat us to the punch. ConnectionListener cl = mcp.getConnection(subject, cri); if (traceEnabled) dump("Got connection from pool tracked by transaction " + cl + " tx=" + trackByTransaction); // Relock and check/set status try { trackByTx.lock(trackByTransaction); } catch (Throwable t) { mcp.returnConnection(cl, false); if (traceEnabled) dump("Had to return connection tracked by transaction " + cl + " tx=" + trackByTransaction + " error=" + t.getMessage()); JBossResourceException.rethrowAsResourceException("Unable to get connection from the pool for tx=" + trackByTransaction, t); } try { // Check we weren't racing with another transaction ConnectionListener other = (ConnectionListener) trackByTx.get(trackByTransaction); if (other != null) { mcp.returnConnection(cl, false); if (traceEnabled) dump("Another thread already got a connection tracked by transaction " + other + " tx=" + trackByTransaction); return other; } // This is the connection for this transaction cl.setTrackByTx(true); trackByTx.set(cl); if (traceEnabled) dump("Using connection from pool tracked by transaction " + cl + " tx=" + trackByTransaction); return cl; } finally { trackByTx.unlock(trackByTransaction); } } public boolean shouldPreFill() { return getPreFill(); } public void prefill() { prefill(null, null, false); } public void prefill(boolean noTxSeperatePool) { prefill(null, null, noTxSeperatePool); } public void prefill(Subject subject, ConnectionRequestInfo cri, boolean noTxSeperatePool) { if (getPreFill()) { log.debug("Attempting to prefill pool for pool with jndi name" + poolName); try { getSubPool(getKey(subject, cri, noTxSeparatePools), subject, cri); } catch (Throwable t) { //No real need to throw here being that pool remains in the same state as before. log.error("Unable to prefill pool with jndi name" + getPoolName(), t); } } } public void returnConnection(ConnectionListener cl, boolean kill) throws ResourceException { cl.setTrackByTx(false); InternalManagedConnectionPool mcp = (InternalManagedConnectionPool) cl.getContext(); mcp.returnConnection(cl, kill); if (traceEnabled) dump("Returning connection to pool " + cl); } /** * Return the inuse count * * @return the count */ public int getInUseConnectionCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionInUseCount(); } } return count; } public int getConnectionCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionCount(); } } return count; } public String getPoolName() { return poolName; } public boolean getPreFill() { return this.poolParams.prefill; } public int getConnectionCreatedCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionCreatedCount(); } } return count; } public int getConnectionDestroyedCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getConnectionDestroyedCount(); } } return count; } public long getAvailableConnectionCount() { long count = 0; synchronized (subPools) { if (subPools.size() == 0) return poolParams.maxSize; for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getAvailableConnections(); } } return count; } public int getMaxConnectionsInUseCount() { int count = 0; synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); count += subPool.getSubPool().getMaxConnectionsInUseCount(); } } return count; } public void shutdown() { synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); subPool.getSubPool().shutdown(); } subPools.clear(); } } public void flush() { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); subPool.getSubPool().shutdown(); } subPools.clear(); } /** * For testing */ protected void shutdownWithoutClear() { synchronized (subPools) { for (Iterator i = subPools.values().iterator(); i.hasNext();) { SubPoolContext subPool = (SubPoolContext) i.next(); subPool.getSubPool().shutdown(); } } } /** * Get any transaction manager associated with the pool * * @return the transaction manager */ protected TransactionManager getTransactionManager() { if (clf != null) return clf.getTransactionManagerInstance(); else return null; } /** * Determine the correct pool for this request, * creates a new one when necessary * * @param key the key to the pool * @param subject the subject of the pool * @param cri the connection request info * @return the subpool context * @throws ResourceException for any error */ protected SubPoolContext getSubPool(Object key, Subject subject, ConnectionRequestInfo cri) throws ResourceException { SubPoolContext subPool = (SubPoolContext) subPools.get(key); if (subPool == null) { TransactionManager tm = getTransactionManager(); subPool = new SubPoolContext(tm, mcf, clf, subject, cri, poolParams, log); synchronized (subPools) { if (subPools.containsKey(key)) subPool = (SubPoolContext) subPools.get(key); else { subPool.initialize(); subPools.put(key, subPool); } } } return subPool; } protected SubPoolContext getSubPool(Object key) { return null; } /** * Dump the stats to the trace log * * @param info some context */ private void dump(String info) { if (traceEnabled) { StringBuffer toLog = new StringBuffer(100); toLog.append(info).append(" [InUse/Available/Max]: ["); toLog.append(this.getInUseConnectionCount()).append("/"); toLog.append(this.getAvailableConnectionCount()).append("/"); toLog.append(this.poolParams.maxSize); toLog.append("]");; log.trace(toLog); } } } /** * Pooling by subject and connection request information */ public static class PoolBySubjectAndCri extends BasePool { public PoolBySubjectAndCri(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { this(mcf, null, poolParams, noTxSeparatePools, log); } public PoolBySubjectAndCri(final ManagedConnectionFactory mcf, final String poolName, final InternalManagedConnectionPool.PoolParams poolParams, final boolean noTxSeparatePools, final Logger log) { super(mcf, poolName, poolParams, noTxSeparatePools, log); } protected Object getKey(final Subject subject, final ConnectionRequestInfo cri, final boolean separateNoTx) throws ResourceException { return new SubjectCriKey(subject, cri, separateNoTx); } public void prefill() { prefill(null, null, false); } public void prefill(boolean noTxSeperatePool) { prefill(null, null, noTxSeperatePool); } public void prefill(Subject subject, ConnectionRequestInfo cri) { prefill(subject, cri, false); } public void prefill(Subject subject, ConnectionRequestInfo cri, boolean noTxSeperatePool) { if (getPreFill()) { log.warn("Prefill pool option was selected for pool with JNDI name " + getPoolName() + " that does not support this feature."); log .warn("Please verify your *-ds.xml file that corresponds with this resource and either remove the true|falsetrue|falsetrue|false