package org.jboss.resource.connectionmanager;
import java.io.PrintWriter;
import java.io.Serializable;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.AccessController;
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 javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.ObjectName;
import javax.naming.InitialContext;
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.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.jboss.deployment.DeploymentException;
import org.jboss.logging.Logger;
import org.jboss.logging.util.LoggerPluginWriter;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.resource.JBossResourceException;
import org.jboss.security.SecurityAssociation;
import org.jboss.security.SubjectSecurityManager;
import org.jboss.system.ServiceMBeanSupport;
public abstract class BaseConnectionManager2
extends ServiceMBeanSupport
implements BaseConnectionManager2MBean, ConnectionCacheListener,
ConnectionListenerFactory
{
private static final String SECURITY_MGR_PATH = "java:/jaas/";
public static final String STOPPING_NOTIFICATION = "jboss.jca.connectionmanagerstopping";
private ObjectName managedConnectionPoolName;
protected ManagedConnectionPool poolingStrategy;
protected String jndiName;
private String securityDomainJndiName;
private SubjectSecurityManager securityDomain;
private ObjectName jaasSecurityManagerService;
private ObjectName ccmName;
private CachedConnectionManager ccm;
protected boolean trace;
protected static void rethrowAsResourceException(String message, Throwable t)
throws ResourceException
{
if (t instanceof ResourceException)
throw (ResourceException) t;
throw new JBossResourceException(message, t);
}
public BaseConnectionManager2()
{
super();
trace = log.isTraceEnabled();
}
public BaseConnectionManager2(CachedConnectionManager ccm,
ManagedConnectionPool poolingStrategy)
{
super();
this.ccm = ccm;
this.poolingStrategy = poolingStrategy;
trace = log.isTraceEnabled();
}
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 ObjectName getJaasSecurityManagerService()
{
return jaasSecurityManagerService;
}
public void setJaasSecurityManagerService(final ObjectName jaasSecurityManagerService)
{
this.jaasSecurityManagerService = jaasSecurityManagerService;
}
public ManagedConnectionFactory getManagedConnectionFactory()
{
return poolingStrategy.getManagedConnectionFactory();
}
public BaseConnectionManager2 getInstance()
{
return this;
}
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 (securityDomainJndiName != null && jaasSecurityManagerService == null)
{
throw new DeploymentException("You must supply both securityDomainJndiName and jaasSecurityManagerService to use container managed security");
}
if (securityDomainJndiName != null)
{
securityDomain = (SubjectSecurityManager)new InitialContext().lookup(SECURITY_MGR_PATH + securityDomainJndiName);
}
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);
String categoryName = poolingStrategy.getManagedConnectionFactory().getClass().getName() + "." + jndiName;
Logger log = Logger.getLogger(categoryName);
PrintWriter logWriter = new LoggerPluginWriter(log.getLoggerPlugin ());
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());
}
}
protected void stopService()
throws Exception
{
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;
securityDomain = null;
ccm = null;
}
public ConnectionListener getManagedConnection(Subject subject, ConnectionRequestInfo cri)
throws ResourceException
{
return getManagedConnection(null, subject, cri);
}
protected ConnectionListener getManagedConnection(Transaction transaction, Subject subject, ConnectionRequestInfo cri)
throws ResourceException
{
return poolingStrategy.getConnection(transaction, subject, cri);
}
public void returnManagedConnection(ConnectionListener cl, boolean kill)
{
ManagedConnectionPool localStrategy = cl.getManagedConnectionPool();
if (localStrategy != poolingStrategy)
kill = true;
try
{
localStrategy.returnConnection(cl, kill);
}
catch (ResourceException re)
{
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();
}
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.");
if (!poolingStrategy.getManagedConnectionFactory().equals(mcf))
throw new ResourceException("Wrong ManagedConnectionFactory sent to allocateConnection!");
Subject subject = getSubject();
ConnectionListener cl = getManagedConnection(subject, cri);
reconnectManagedConnection(cl);
Object connection = null;
try
{
connection = cl.getManagedConnection().getConnection(subject, cri);
}
catch (Throwable t)
{
disconnectManagedConnection(cl);
rethrowAsResourceException("Unchecked throwable in ManagedConnection.getConnection()", t);
}
registerAssociation(cl, connection);
if (ccm != null)
ccm.registerConnection(this, cl, connection, cri);
return connection;
}
public void transactionStarted(Collection conns) throws SystemException
{
}
public void reconnect(Collection conns, Set unsharableResources) throws ResourceException
{
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)
{
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);
reconnectManagedConnection(cl);
}
cl.getManagedConnection().associateConnection(cr.connection);
registerAssociation(cl, cr.connection);
cr.setConnectionListener(cl);
} criToCLMap.clear(); }
public void disconnect(Collection crs, Set unsharableResources) throws ResourceException
{
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());
}
public MBeanNotificationInfo[] getNotificationInfo()
{
return super.getNotificationInfo();
}
protected void unregisterAssociation(ConnectionListener cl, Object c) throws ResourceException
{
cl.unregisterConnection(c);
}
protected final CachedConnectionManager getCcm()
{
return ccm;
}
protected void reconnectManagedConnection(ConnectionListener cl) throws ResourceException
{
try
{
managedConnectionReconnected(cl);
}
catch (Throwable t)
{
disconnectManagedConnection(cl);
rethrowAsResourceException("Unchecked throwable in managedConnectionReconnected()", t);
}
}
protected void disconnectManagedConnection(ConnectionListener cl)
{
try
{
managedConnectionDisconnected(cl);
}
catch (Throwable t)
{
log.warn("Unchecked throwable in managedConnectionDisconnected()", t);
}
}
protected void managedConnectionReconnected(ConnectionListener cl) throws ResourceException
{
}
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 (securityDomain != null)
{
Principal principal = GetPrincipalAction.getPrincipal();
Object credential = GetCredentialAction.getCredential();
subject = new Subject();
if (securityDomain.isValid(principal, credential, subject) == false)
{
throw new SecurityException("Invalid authentication attempt, principal=" + principal);
} } if (trace)
log.trace("subject: " + subject);
return subject;
}
public boolean isTransactional()
{
return false;
}
public TransactionManager getTransactionManagerInstance()
{
return null;
}
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 boolean trackByTx = false;
private boolean permit = false;
protected Logger log;
protected boolean trace;
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;
}
public void setTrackByTx(boolean trackByTx)
{
this.trackByTx = trackByTx;
}
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 (ce != null)
log.warn("Connection error occured: " + this, ce.getException());
else
log.warn("Unknown Connection error occured: " + this);
try
{
unregisterConnections();
}
catch (Exception e)
{
}
if (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 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);
buffer.append(" mcp=").append(mcp);
buffer.append(" context=").append(context);
buffer.append(']');
return buffer.toString();
}
}
public static class ConnectionManagerProxy
implements ConnectionManager, Serializable
{
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;
}
public Object allocateConnection(ManagedConnectionFactory mcf, ConnectionRequestInfo cri) throws ResourceException
{
return getCM().allocateConnection(mcf, cri);
}
private BaseConnectionManager2 getCM() throws ResourceException
{
if (realCm == null)
{
try
{
realCm = (BaseConnectionManager2)MBeanServerLocator.locateJBoss().getAttribute(
cmName,
"Instance");
}
catch (Throwable t)
{
Throwable t2 = JMXExceptionDecoder.decode(t);
throw new ResourceException("Problem locating real ConnectionManager: " + t2);
} } return realCm;
}
}
private static class GetPrincipalAction implements PrivilegedAction
{
static PrivilegedAction ACTION = new GetPrincipalAction();
public Object run()
{
Principal principal = SecurityAssociation.getPrincipal();
return principal;
}
static Principal getPrincipal()
{
Principal principal = (Principal) AccessController.doPrivileged(ACTION);
return principal;
}
}
private static class GetCredentialAction implements PrivilegedAction
{
static PrivilegedAction ACTION = new GetCredentialAction();
public Object run()
{
Object credential = SecurityAssociation.getCredential();
return credential;
}
static Object getCredential()
{
Object credential = AccessController.doPrivileged(ACTION);
return credential;
}
}
}