/**
* Alipay.com Inc.
* Copyright (c) 2004-2012 All Rights Reserved.
*/
package com.alipay.zdal.datasource.resource.connectionmanager;
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.security.auth.Subject;
import org.apache.log4j.Logger;
import com.alipay.zdal.datasource.Service;
import com.alipay.zdal.datasource.resource.JBossResourceException;
import com.alipay.zdal.datasource.resource.ResourceException;
import com.alipay.zdal.datasource.resource.spi.ConnectionEvent;
import com.alipay.zdal.datasource.resource.spi.ConnectionRequestInfo;
import com.alipay.zdal.datasource.resource.spi.ManagedConnection;
import com.alipay.zdal.datasource.resource.spi.ManagedConnectionFactory;
import com.alipay.zdal.datasource.resource.util.NotImplementedException;
import com.alipay.zdal.datasource.tm.TransactionTimeoutConfiguration;
import com.alipay.zdal.datasource.transaction.RollbackException;
import com.alipay.zdal.datasource.transaction.SystemException;
import com.alipay.zdal.datasource.transaction.Transaction;
import com.alipay.zdal.datasource.transaction.TransactionManager;
/**
* 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 ����
* @version $Id: BaseConnectionManager2.java, v 0.1 2014-1-6 ����05:32:58 Exp $
*/
public abstract class BaseConnectionManager2 implements ConnectionCacheListener,
ConnectionListenerFactory,
TransactionTimeoutConfiguration, Service {
private static final Logger log = Logger.getLogger(BaseConnectionManager2.class);
protected ManagedConnectionPool poolingStrategy;
protected String name;
protected CachedConnectionManager ccm;
protected boolean trace;
/**
* Rethrow a throwable as resource exception
*
* @deprecated use JBossResourceException.rethrowAsResourceException
*/
@Deprecated
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.isDebugEnabled();
}
/**
* 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.isDebugEnabled();
}
public void setCachedConnectionManager(CachedConnectionManager cachedConnectionManager) {
this.ccm = cachedConnectionManager;
}
/**
* For testing
*/
public ManagedConnectionPool getPoolingStrategy() {
return poolingStrategy;
}
public void setPoolingStrategy(ManagedConnectionPool poolingStrategy) {
this.poolingStrategy = poolingStrategy;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ManagedConnectionFactory getManagedConnectionFactory() {
return poolingStrategy.getManagedConnectionFactory();
}
public BaseConnectionManager2 getInstance() {
return this;
}
public long getTimeLeftBeforeTransactionTimeout(boolean errorRollback) throws RollbackException {
return -1;
}
public int getTransactionTimeout() throws SystemException {
throw new NotImplementedException("NYI: getTransactionTimeout()");
}
/**
* @see com.alipay.zdal.datasource.Service#startService()
*/
public void startService() {
poolingStrategy.setConnectionListenerFactory(this);
// Give it somewhere to tell people things
// String categoryName = poolingStrategy.getManagedConnectionFactory().getClass().getName()
// + "." + name;
// LogWriterû��
// 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());
// }
if (poolingStrategy instanceof PreFillPoolSupport) {
PreFillPoolSupport prefill = (PreFillPoolSupport) poolingStrategy;
if (prefill.shouldPreFill()) {
prefill.prefill();
}
}
}
/**
* @see com.alipay.zdal.datasource.Service#stopService()
*/
public void stopService() throws Exception {
poolingStrategy.setConnectionListenerFactory(null);
poolingStrategy = 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 {
return poolingStrategy.getConnection(transaction, subject, cri);
}
/**
*
* @param cl
* @param kill
*/
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 (log.isDebugEnabled() && kill) {
log.debug("resourceException killing connection (error retrieving from pool?)", re);
} else if (log.isDebugEnabled() && !kill) {
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) {
managedConnectionDisconnected(cl);
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
/**
* @see com.alipay.zdal.datasource.resource.connectionmanager.ConnectionCacheListener#transactionStarted(java.util.Collection)
*/
public void transactionStarted(Collection conns) throws SystemException {
// reimplement in subclasses
}
/**
* @see com.alipay.zdal.datasource.resource.connectionmanager.ConnectionCacheListener#reconnect(java.util.Collection, java.util.Set)
*/
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(name)) {
if (log.isDebugEnabled()) {
log.debug("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.
}
/**
* @see com.alipay.zdal.datasource.resource.connectionmanager.ConnectionCacheListener#disconnect(java.util.Collection, java.util.Set)
*/
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(name)) {
if (log.isDebugEnabled()) {
log.debug("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());
}
// 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) throws ResourceException {
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 {
}
/**
*
* @param cl
* @param c
* @throws ResourceException
*/
private void registerAssociation(ConnectionListener cl, Object c) throws ResourceException {
cl.registerConnection(c);
}
private Subject getSubject() {
// TODO: ��ǰ��֧��Subject
Subject subject = null;
// if (securityDomain != null)
// {
// /* Authenticate using the caller info and obtain a copy of the
// Subject
// state for use in establishing a secure connection. A copy must be
// obtained to avoid problems with multiple threads associated with
// the same principal changing the state of the resulting Subject.
// */
// 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);
// }
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 boolean trackByTx = 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.isDebugEnabled();
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 void tidyup() throws ResourceException {
}
public synchronized void registerConnection(Object handle) {
handles.add(handle);
}
/**
* @see com.alipay.zdal.datasource.resource.connectionmanager.ConnectionListener#unregisterConnection(java.lang.Object)
*/
public synchronized void unregisterConnection(Object handle) {
if (!handles.remove(handle)) {
if (log.isDebugEnabled()) {
log.debug("Unregistered handle that was not registered! " + handle
+ " for managedConnection: " + mc);
}
}
if (trace) {
if (log.isDebugEnabled()) {
log.debug("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();
}
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ConnectionEventListener#connectionErrorOccurred(com.alipay.zdal.datasource.resource.spi.ConnectionEvent)
*/
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
/**
* @see java.lang.Object#toString()
*/
@Override
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);
toString(buffer);
buffer.append(']');
return buffer.toString();
}
// For debugging
protected void toString(StringBuffer buffer) {
}
}
// 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;
// }
// }
}