/*
* 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.io.PrintWriter;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.ObjectName;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.jboss.deployers.spi.DeploymentException;
import org.jboss.logging.Logger;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.resource.JBossResourceException;
import org.jboss.security.SubjectFactory;
import org.jboss.stdio.LoggingWriter;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.tm.TransactionTimeoutConfiguration;
import org.jboss.util.NestedRuntimeException;
import org.jboss.util.NotImplementedException;
/**
* The BaseConnectionManager2 is an abstract base class for JBoss ConnectionManager
* implementations. It includes functionality to obtain managed connections from
* a ManagedConnectionPool mbean, find the Subject from a SubjectSecurityDomain,
* and interact with the CachedConnectionManager for connections held over
* transaction and method boundaries. Important mbean references are to a
* ManagedConnectionPool supplier (typically a JBossManagedConnectionPool), and a
* RARDeployment representing the ManagedConnectionFactory.
*
*
* @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
* @author <a href="mailto:E.Guib@ceyoniq.com">Erwin Guib</a>
* @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
* @author <a href="weston.price@jboss.com">Weston Price</a>
* @author Anil.Saldhana@redhat.com
*
* @version $Revision: 103541 $
*/
@SuppressWarnings("unchecked")
public abstract class BaseConnectionManager2 extends ServiceMBeanSupport
implements
BaseConnectionManager2MBean,
ConnectionCacheListener,
ConnectionListenerFactory,
TransactionTimeoutConfiguration,
JTATransactionChecker
{
/**
* Note that this copy has a trailing / unlike the original in
* JaasSecurityManagerService.
*/
private static final String SECURITY_MGR_PATH = "java:/jaas/";
public static final String STOPPING_NOTIFICATION = "jboss.jca.connectionmanagerstopping";
protected ObjectName managedConnectionPoolName;
protected ManagedConnectionPool poolingStrategy;
protected String jndiName;
protected String securityDomainJndiName;
protected SubjectFactory subjectFactory;
protected ObjectName jaasSecurityManagerService;
protected ObjectName ccmName;
protected CachedConnectionManager ccm;
protected boolean trace;
protected int allocationRetry;
protected long allocationRetryWaitMillis;
protected AtomicBoolean shutdown = new AtomicBoolean(false);
/**
* Rethrow a throwable as resource exception
*
* @deprecated use JBossResourceException.rethrowAsResourceException
*/
protected static void rethrowAsResourceException(String message, Throwable t) throws ResourceException
{
JBossResourceException.rethrowAsResourceException(message, t);
}
/**
* Default BaseConnectionManager2 managed constructor for use by subclass mbeans.
*/
public BaseConnectionManager2()
{
super();
trace = log.isTraceEnabled();
}
/**
* Creates a new <code>BaseConnectionManager2</code> instance.
* for TESTING ONLY! not a managed operation.
* @param ccm a <code>CachedConnectionManager</code> value
* @param poolingStrategy a <code>ManagedConnectionPool</code> value
*/
public BaseConnectionManager2(CachedConnectionManager ccm, ManagedConnectionPool poolingStrategy)
{
super();
this.ccm = ccm;
this.poolingStrategy = poolingStrategy;
trace = log.isTraceEnabled();
}
/**
* For testing
*/
public ManagedConnectionPool getPoolingStrategy()
{
return poolingStrategy;
}
public String getJndiName()
{
return jndiName;
}
public void setJndiName(String jndiName)
{
this.jndiName = jndiName;
}
public ObjectName getManagedConnectionPool()
{
return managedConnectionPoolName;
}
public void setManagedConnectionPool(ObjectName newManagedConnectionPool)
{
this.managedConnectionPoolName = newManagedConnectionPool;
}
public void setCachedConnectionManager(ObjectName ccmName)
{
this.ccmName = ccmName;
}
public ObjectName getCachedConnectionManager()
{
return ccmName;
}
public void setSecurityDomainJndiName(String securityDomainJndiName)
{
if (securityDomainJndiName != null && securityDomainJndiName.startsWith(SECURITY_MGR_PATH))
{
securityDomainJndiName = securityDomainJndiName.substring(SECURITY_MGR_PATH.length());
log.warn("WARNING: UPDATE YOUR SecurityDomainJndiName! REMOVE " + SECURITY_MGR_PATH);
}
this.securityDomainJndiName = securityDomainJndiName;
}
public String getSecurityDomainJndiName()
{
return securityDomainJndiName;
}
public SubjectFactory getSubjectFactory()
{
return subjectFactory;
}
public void setSubjectFactory(SubjectFactory subjectFactory)
{
this.subjectFactory = subjectFactory;
}
/**
* @deprecated
*/
public ObjectName getJaasSecurityManagerService()
{
return this.jaasSecurityManagerService;
}
/**
* @deprecated Maintained for legacy
*/
public void setJaasSecurityManagerService(final ObjectName jaasSecurityManagerService)
{
this.jaasSecurityManagerService = jaasSecurityManagerService;
}
public ManagedConnectionFactory getManagedConnectionFactory()
{
return poolingStrategy.getManagedConnectionFactory();
}
public BaseConnectionManager2 getInstance()
{
return this;
}
/**
* Set the number of allocation retries
* @param number
*/
public void setAllocationRetry(int number)
{
if (number >= 0)
allocationRetry = number;
}
/**
* Get the number of allocation retries
* @return The number of retries
*/
public int getAllocationRetry()
{
return allocationRetry;
}
/**
* Set the wait time between each allocation retry
* @param millis
*/
public void setAllocationRetryWaitMillis(long millis)
{
if (millis > 0)
allocationRetryWaitMillis = millis;
}
/**
* Get the wait time between each allocation retry
* @return The millis
*/
public long getAllocationRetryWaitMillis()
{
return allocationRetryWaitMillis;
}
public long getTimeLeftBeforeTransactionTimeout(boolean errorRollback) throws RollbackException
{
return -1;
}
public int getTransactionTimeout() throws SystemException
{
throw new NotImplementedException("NYI: getTransactionTimeout()");
}
public void checkTransactionActive() throws RollbackException, SystemException
{
// Nothing
}
//ServiceMBeanSupport
protected void startService() throws Exception
{
try
{
ccm = (CachedConnectionManager) server.getAttribute(ccmName, "Instance");
}
catch (Exception e)
{
JMXExceptionDecoder.rethrow(e);
}
if (ccm == null)
throw new DeploymentException("cached ConnectionManager not found: " + ccmName);
if (managedConnectionPoolName == null)
throw new DeploymentException("managedConnectionPool not set!");
try
{
poolingStrategy = (ManagedConnectionPool) server.getAttribute(managedConnectionPoolName,
"ManagedConnectionPool");
}
catch (Exception e)
{
JMXExceptionDecoder.rethrow(e);
}
poolingStrategy.setConnectionListenerFactory(this);
// Give it somewhere to tell people things
String categoryName = poolingStrategy.getManagedConnectionFactory().getClass().getName() + "." + jndiName;
PrintWriter logWriter = new PrintWriter(new LoggingWriter(categoryName, "INFO"));
try
{
poolingStrategy.getManagedConnectionFactory().setLogWriter(logWriter);
}
catch (ResourceException re)
{
log.warn("Unable to set log writer '" + logWriter + "' on " + "managed connection factory", re);
log.warn("Linked exception:", re.getLinkedException());
}
if (poolingStrategy instanceof PreFillPoolSupport)
{
PreFillPoolSupport prefill = (PreFillPoolSupport) poolingStrategy;
if (prefill.shouldPreFill())
prefill.prefill();
}
shutdown.set(false);
}
protected void stopService() throws Exception
{
shutdown.set(true);
//notify the login modules the mcf is going away, they need to look it up again later.
sendNotification(new Notification(STOPPING_NOTIFICATION, getServiceName(), getNextNotificationSequenceNumber()));
/*
* if (jaasSecurityManagerService != null && securityDomainJndiName != null)
server.invoke(jaasSecurityManagerService, "flushAuthenticationCache", new Object[] { securityDomainJndiName }, new String[] { String.class.getName() });
*/
poolingStrategy.setConnectionListenerFactory(null);
poolingStrategy = null;
subjectFactory = null;
ccm = null;
}
/**
* Public for use in testing pooling functionality by itself.
* called by both allocateConnection and reconnect.
*
* @param subject a <code>Subject</code> value
* @param cri a <code>ConnectionRequestInfo</code> value
* @return a <code>ManagedConnection</code> value
* @exception ResourceException if an error occurs
*/
public ConnectionListener getManagedConnection(Subject subject, ConnectionRequestInfo cri) throws ResourceException
{
return getManagedConnection(null, subject, cri);
}
/**
* Get the managed connection from the pool
*
* @param transaction the transaction for track by transaction
* @param subject the subject
* @param cri the ConnectionRequestInfo
* @return a managed connection
* @exception ResourceException if an error occurs
*/
protected ConnectionListener getManagedConnection(Transaction transaction, Subject subject, ConnectionRequestInfo cri)
throws ResourceException
{
ResourceException failure = null;
if (shutdown.get())
throw new ResourceException("The connection manager is shutdown " + jndiName);
// First attempt
try
{
return poolingStrategy.getConnection(transaction, subject, cri);
}
catch (ResourceException e)
{
failure = e;
// Retry?
if (allocationRetry != 0)
{
for (int i = 0; i < allocationRetry; i++)
{
if (shutdown.get())
throw new ResourceException("The connection manager is shutdown " + jndiName);
if (trace)
log.trace("Attempting allocation retry for cri=" + cri);
try
{
if (allocationRetryWaitMillis != 0)
Thread.sleep(allocationRetryWaitMillis);
return poolingStrategy.getConnection(transaction, subject, cri);
}
catch (ResourceException e1)
{
failure = e1;
}
catch (InterruptedException e1)
{
Thread.currentThread().interrupt();
JBossResourceException.rethrowAsResourceException("getManagedConnection retry wait was interrupted " + jndiName, e1);
}
}
}
}
// If we get here all retries failed, throw the lastest failure
throw new ResourceException("Unable to get managed connection for " + jndiName, failure);
}
public void returnManagedConnection(ConnectionListener cl, boolean kill)
{
ManagedConnectionPool localStrategy = cl.getManagedConnectionPool();
if (localStrategy != poolingStrategy)
kill = true;
try
{
if (kill == false && cl.getState() == ConnectionListener.NORMAL)
cl.tidyup();
}
catch (Throwable t)
{
log.warn("Error during tidyup " + cl, t);
kill = true;
}
try
{
localStrategy.returnConnection(cl, kill);
}
catch (ResourceException re)
{
// We can receive notification of an error on the connection
// before it has been assigned to the pool. Reduce the noise for
// these errors
if (kill)
log.debug("resourceException killing connection (error retrieving from pool?)", re);
else
log.warn("resourceException returning connection: " + cl.getManagedConnection(), re);
}
}
public int getConnectionCount()
{
return poolingStrategy.getConnectionCount();
}
// implementation of javax.resource.spi.ConnectionManager interface
public Object allocateConnection(ManagedConnectionFactory mcf, ConnectionRequestInfo cri) throws ResourceException
{
if (poolingStrategy == null)
throw new ResourceException(
"You are trying to use a connection factory that has been shut down: ManagedConnectionFactory is null.");
//it is an explicit spec requirement that equals be used for matching rather than ==.
if (!poolingStrategy.getManagedConnectionFactory().equals(mcf))
throw new ResourceException("Wrong ManagedConnectionFactory sent to allocateConnection!");
// Pick a managed connection from the pool
Subject subject = getSubject();
ConnectionListener cl = getManagedConnection(subject, cri);
// Tell each connection manager the managed connection is active
reconnectManagedConnection(cl);
// Ask the managed connection for a connection
Object connection = null;
try
{
connection = cl.getManagedConnection().getConnection(subject, cri);
}
catch (Throwable t)
{
try {
managedConnectionDisconnected(cl);
}
catch (ResourceException re)
{
log.trace("Get exception from managedConnectionDisconnected, maybe delist() have problem" + re);
returnManagedConnection(cl, true);
}
JBossResourceException.rethrowAsResourceException(
"Unchecked throwable in ManagedConnection.getConnection() cl=" + cl, t);
}
// Associate managed connection with the connection
registerAssociation(cl, connection);
if (ccm != null)
ccm.registerConnection(this, cl, connection, cri);
return connection;
}
// ConnectionCacheListener implementation
public void transactionStarted(Collection conns) throws SystemException
{
//reimplement in subclasses
}
public void reconnect(Collection conns, Set unsharableResources) throws ResourceException
{
// if we have an unshareable connection the association was not removed
// nothing to do
if (unsharableResources.contains(jndiName))
{
log.trace("reconnect for unshareable connection: nothing to do");
return;
}
Map criToCLMap = new HashMap();
for (Iterator i = conns.iterator(); i.hasNext();)
{
ConnectionRecord cr = (ConnectionRecord) i.next();
if (cr.cl != null)
{
//This might well be an error.
log.warn("reconnecting a connection handle that still has a managedConnection! "
+ cr.cl.getManagedConnection() + " " + cr.connection);
}
ConnectionListener cl = (ConnectionListener) criToCLMap.get(cr.cri);
if (cl == null)
{
cl = getManagedConnection(getSubject(), cr.cri);
criToCLMap.put(cr.cri, cl);
//only call once per managed connection, when we get it.
reconnectManagedConnection(cl);
}
cl.getManagedConnection().associateConnection(cr.connection);
registerAssociation(cl, cr.connection);
cr.setConnectionListener(cl);
}
criToCLMap.clear();//not needed logically, might help the gc.
}
public void disconnect(Collection crs, Set unsharableResources) throws ResourceException
{
// if we have an unshareable connection do not remove the association
// nothing to do
if (unsharableResources.contains(jndiName))
{
log.trace("disconnect for unshareable connection: nothing to do");
return;
}
Set cls = new HashSet();
for (Iterator i = crs.iterator(); i.hasNext();)
{
ConnectionRecord cr = (ConnectionRecord) i.next();
ConnectionListener cl = cr.cl;
cr.setConnectionListener(null);
unregisterAssociation(cl, cr.connection);
if (!cls.contains(cl))
{
cls.add(cl);
}
}
for (Iterator i = cls.iterator(); i.hasNext();)
disconnectManagedConnection((ConnectionListener) i.next());
}
// implementation of javax.management.NotificationBroadcaster interface
public MBeanNotificationInfo[] getNotificationInfo()
{
// TODO: implement this javax.management.NotificationBroadcaster method
return super.getNotificationInfo();
}
//protected methods
//does NOT put the mc back in the pool if no more handles. Doing so would introduce a race condition
//whereby the mc got back in the pool while still enlisted in the tx.
//The mc could be checked out again and used before the delist occured.
protected void unregisterAssociation(ConnectionListener cl, Object c)
{
cl.unregisterConnection(c);
}
/**
* Invoked to reassociate a managed connection
*
* @param cl the managed connection
*/
protected void reconnectManagedConnection(ConnectionListener cl) throws ResourceException
{
try
{
//WRONG METHOD NAME!!
managedConnectionReconnected(cl);
}
catch (Throwable t)
{
disconnectManagedConnection(cl);
JBossResourceException.rethrowAsResourceException("Unchecked throwable in managedConnectionReconnected() cl="
+ cl, t);
}
}
/**
* Invoked when a managed connection is no longer associated
*
* @param cl the managed connection
*/
protected void disconnectManagedConnection(ConnectionListener cl)
{
try
{
managedConnectionDisconnected(cl);
}
catch (Throwable t)
{
log.warn("Unchecked throwable in managedConnectionDisconnected() cl=" + cl, t);
}
}
protected final CachedConnectionManager getCcm()
{
return ccm;
}
/**
* For polymorphism.<p>
*
* Do not invoke directly use reconnectManagedConnection
* which does the relevent exception handling
*/
protected void managedConnectionReconnected(ConnectionListener cl) throws ResourceException
{
}
/**
* For polymorphism.<p>
*
* Do not invoke directly use disconnectManagedConnection
* which does the relevent exception handling
*/
protected void managedConnectionDisconnected(ConnectionListener cl) throws ResourceException
{
}
private void registerAssociation(ConnectionListener cl, Object c) throws ResourceException
{
cl.registerConnection(c);
}
private Subject getSubject()
{
Subject subject = null;
if(subjectFactory != null && securityDomainJndiName != null)
{
subject = subjectFactory.createSubject(securityDomainJndiName);
}
if (trace)
log.trace("subject: " + subject);
return subject;
}
// ConnectionListenerFactory
public boolean isTransactional()
{
return false;
}
public TransactionManager getTransactionManagerInstance()
{
return null;
}
//ConnectionListener
protected abstract class BaseConnectionEventListener implements ConnectionListener
{
private final ManagedConnection mc;
private final ManagedConnectionPool mcp;
private final Object context;
private int state = NORMAL;
private final List handles = new LinkedList();
private long lastUse;
private AtomicBoolean trackByTx = new AtomicBoolean(false);
private boolean permit = false;
protected Logger log;
protected boolean trace;
protected long lastValidated;
protected BaseConnectionEventListener(ManagedConnection mc, ManagedConnectionPool mcp, Object context, Logger log)
{
this.mc = mc;
this.mcp = mcp;
this.context = context;
this.log = log;
trace = log.isTraceEnabled();
lastUse = System.currentTimeMillis();
}
public ManagedConnection getManagedConnection()
{
return mc;
}
public ManagedConnectionPool getManagedConnectionPool()
{
return mcp;
}
public Object getContext()
{
return context;
}
public int getState()
{
return state;
}
public void setState(int newState)
{
this.state = newState;
}
public boolean isTimedOut(long timeout)
{
return lastUse < timeout;
}
public void used()
{
lastUse = System.currentTimeMillis();
}
public boolean isTrackByTx()
{
return trackByTx.get();
}
public void setTrackByTx(boolean trackByTx)
{
this.trackByTx.set(trackByTx);
}
public void tidyup() throws ResourceException
{
}
public synchronized void registerConnection(Object handle)
{
handles.add(handle);
}
public synchronized void unregisterConnection(Object handle)
{
if (!handles.remove(handle))
{
log.info("Unregistered handle that was not registered! " + handle + " for managedConnection: " + mc);
}
if (trace)
log.trace("unregisterConnection: " + handles.size() + " handles left");
}
public synchronized boolean isManagedConnectionFree()
{
return handles.isEmpty();
}
protected synchronized void unregisterConnections()
{
try
{
for (Iterator i = handles.iterator(); i.hasNext();)
{
getCcm().unregisterConnection(BaseConnectionManager2.this, i.next());
}
}
finally
{
handles.clear();
}
}
public void connectionErrorOccurred(ConnectionEvent ce)
{
if (state == NORMAL)
{
if (ce != null)
{
Throwable t = ce.getException();
if (t == null)
t = new Exception("No exception was reported");
log.warn("Connection error occured: " + this, t);
}
else
{
Throwable t = new Exception("No exception was reported");
log.warn("Unknown Connection error occured: " + this, t);
}
}
try
{
unregisterConnections();
}
catch (Throwable t)
{
//ignore, it wasn't checked out.
}
if (ce != null && ce.getSource() != getManagedConnection())
log.warn("Notified of error on a different managed connection?");
returnManagedConnection(this, true);
}
public void enlist() throws SystemException
{
}
public void delist() throws ResourceException
{
}
public boolean hasPermit()
{
return permit;
}
public void grantPermit(boolean value)
{
this.permit = value;
}
public long getLastValidatedTime()
{
return this.lastValidated;
}
public void setLastValidatedTime(long lastValidated)
{
this.lastValidated = lastValidated;
}
// For debugging
public String toString()
{
StringBuffer buffer = new StringBuffer(100);
buffer.append(getClass().getName()).append('@').append(Integer.toHexString(System.identityHashCode(this)));
buffer.append("[state=");
if (state == ConnectionListener.NORMAL)
buffer.append("NORMAL");
else if (state == ConnectionListener.DESTROY)
buffer.append("DESTROY");
else if (state == ConnectionListener.DESTROYED)
buffer.append("DESTROYED");
else
buffer.append("UNKNOWN?");
buffer.append(" mc=").append(mc);
buffer.append(" handles=").append(handles.size());
buffer.append(" lastUse=").append(lastUse);
buffer.append(" permit=").append(permit);
buffer.append(" trackByTx=").append(trackByTx.get());
buffer.append(" mcp=").append(mcp);
buffer.append(" context=").append(context);
toString(buffer);
buffer.append(']');
return buffer.toString();
}
// For debugging
protected void toString(StringBuffer buffer)
{
}
}
public static class ConnectionManagerProxy
implements
ConnectionManager,
Serializable,
TransactionTimeoutConfiguration,
JTATransactionChecker
{
static final long serialVersionUID = -528322728929261214L;
private transient BaseConnectionManager2 realCm;
private final ObjectName cmName;
ConnectionManagerProxy(final BaseConnectionManager2 realCm, final ObjectName cmName)
{
this.realCm = realCm;
this.cmName = cmName;
}
// implementation of javax.resource.spi.ConnectionManager interface
public Object allocateConnection(ManagedConnectionFactory mcf, ConnectionRequestInfo cri)
throws ResourceException
{
return getCM().allocateConnection(mcf, cri);
}
public long getTimeLeftBeforeTransactionTimeout(boolean errorRollback) throws RollbackException
{
try
{
return getCM().getTimeLeftBeforeTransactionTimeout(errorRollback);
}
catch (ResourceException e)
{
throw new NestedRuntimeException("Unable to retrieve connection manager", e);
}
}
public int getTransactionTimeout() throws SystemException
{
try
{
return getCM().getTransactionTimeout();
}
catch (ResourceException e)
{
throw new NestedRuntimeException("Unable to retrieve connection manager", e);
}
}
public void checkTransactionActive() throws RollbackException, SystemException
{
try
{
getCM().checkTransactionActive();
}
catch (ResourceException e)
{
throw new NestedRuntimeException("Unable to retrieve connection manager", e);
}
}
private BaseConnectionManager2 getCM() throws ResourceException
{
if (realCm == null)
{
try
{
MBeanServer server = MBeanServerLocator.locateJBoss();
realCm = (BaseConnectionManager2) server.getAttribute(cmName, "Instance");
}
catch (Throwable t)
{
Throwable t2 = JMXExceptionDecoder.decode(t);
JBossResourceException.rethrowAsResourceException("Problem locating real ConnectionManager: " + cmName,
t2);
}
}
return realCm;
}
}
}