/**
* Alipay.com Inc.
* Copyright (c) 2004-2012 All Rights Reserved.
*/
package com.alipay.zdal.datasource.resource.adapter.jdbc;
import java.io.PrintWriter;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.security.auth.Subject;
import org.apache.log4j.Logger;
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.ConnectionEventListener;
import com.alipay.zdal.datasource.resource.spi.ConnectionRequestInfo;
import com.alipay.zdal.datasource.resource.spi.ManagedConnection;
import com.alipay.zdal.datasource.resource.spi.ManagedConnectionMetaData;
import com.alipay.zdal.datasource.resource.spi.ResourceAdapterInternalException;
/**
* BaseWrapperManagedConnection
*
*
* @author ����
* @version $Id: BaseWrapperManagedConnection.java, v 0.1 2014-1-6 ����05:27:08 Exp $
*/
public abstract class BaseWrapperManagedConnection implements ManagedConnection {
protected final BaseWrapperManagedConnectionFactory mcf;
protected final Connection con;
protected final Properties props;
private final int transactionIsolation;
private final boolean readOnly;
/** cel = ConnectionEventListener */
private final Collection cels = new ArrayList();
private final Set handles = new HashSet();
private PreparedStatementCache psCache = null;
protected final Object stateLock = new Object();
protected boolean inManagedTransaction = false;
protected AtomicBoolean inLocalTransaction = new AtomicBoolean(
false);
protected boolean jdbcAutoCommit = true;
protected boolean underlyingAutoCommit = true;
protected boolean jdbcReadOnly;
protected boolean underlyingReadOnly;
protected int jdbcTransactionIsolation;
protected boolean destroyed = false;
/**
* @param mcf
* @param con
* @param props
* @param transactionIsolation
* @param psCacheSize
* @throws SQLException
*/
public BaseWrapperManagedConnection(final BaseWrapperManagedConnectionFactory mcf,
final Connection con, final Properties props,
final int transactionIsolation, final int psCacheSize)
throws SQLException {
this.mcf = mcf;
this.con = con;
this.props = props;
if (psCacheSize > 0)
psCache = new PreparedStatementCache(psCacheSize);
if (transactionIsolation == -1)
this.transactionIsolation = con.getTransactionIsolation();
else {
this.transactionIsolation = transactionIsolation;
con.setTransactionIsolation(transactionIsolation);
}
readOnly = con.isReadOnly();
if (mcf.getNewConnectionSQL() != null) {
Statement s = con.createStatement();
try {
s.execute(mcf.getNewConnectionSQL());
} finally {
s.close();
}
}
underlyingReadOnly = readOnly;
jdbcReadOnly = readOnly;
jdbcTransactionIsolation = this.transactionIsolation;
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#addConnectionEventListener(com.alipay.zdal.datasource.resource.spi.ConnectionEventListener)
*/
public void addConnectionEventListener(ConnectionEventListener cel) {
synchronized (cels) {
cels.add(cel);
}
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#removeConnectionEventListener(com.alipay.zdal.datasource.resource.spi.ConnectionEventListener)
*/
public void removeConnectionEventListener(ConnectionEventListener cel) {
synchronized (cels) {
cels.remove(cel);
}
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#associateConnection(java.lang.Object)
*/
public void associateConnection(Object handle) throws ResourceException {
if (!(handle instanceof WrappedConnection))
throw new JBossResourceException("Wrong kind of connection handle to associate"
+ handle);
((WrappedConnection) handle).setManagedConnection(this);
synchronized (handles) {
handles.add(handle);
}
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#getLogWriter()
*/
public PrintWriter getLogWriter() throws ResourceException {
// TODO: implement this javax.resource.spi.ManagedConnection method
return null;
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#getMetaData()
*/
public ManagedConnectionMetaData getMetaData() throws ResourceException {
// TODO: implement this javax.resource.spi.ManagedConnection method
return null;
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#setLogWriter(java.io.PrintWriter)
*/
public void setLogWriter(PrintWriter param1) throws ResourceException {
// TODO: implement this javax.resource.spi.ManagedConnection method
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#cleanup()
*/
public void cleanup() throws ResourceException {
synchronized (handles) {
for (Iterator i = handles.iterator(); i.hasNext();) {
WrappedConnection lc = (WrappedConnection) i.next();
lc.setManagedConnection(null);
}
handles.clear();
}
//reset all the properties we know about to defaults.
synchronized (stateLock) {
jdbcAutoCommit = true;
jdbcReadOnly = readOnly;
if (jdbcTransactionIsolation != transactionIsolation) {
try {
con.setTransactionIsolation(jdbcTransactionIsolation);
jdbcTransactionIsolation = transactionIsolation;
} catch (SQLException e) {
mcf.log.warn("Error resetting transaction isolation ", e);
}
}
}
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#getConnection(javax.security.auth.Subject, com.alipay.zdal.datasource.resource.spi.ConnectionRequestInfo)
*/
public Object getConnection(Subject subject, ConnectionRequestInfo cri)
throws ResourceException {
checkIdentity(subject, cri);
WrappedConnection lc = new WrappedConnection(this);
synchronized (handles) {
handles.add(lc);
}
return lc;
}
/**
* @see com.alipay.zdal.datasource.resource.spi.ManagedConnection#destroy()
*/
public void destroy() throws ResourceException {
synchronized (stateLock) {
destroyed = true;
}
cleanup();
try {
con.close();
} catch (SQLException ignored) {
getLog().error("Ignored error during close: ", ignored);
}
}
/**
*
* @return
*/
public boolean checkValid() {
SQLException e = mcf.isValidConnection(con);
if (e == null)
// It's ok
return true;
else {
getLog().warn(
"Destroying connection that is not valid, due to the following exception: " + con,
e);
broadcastConnectionError(e);
return false;
}
}
void closeHandle(WrappedConnection handle) {
synchronized (stateLock) {
if (destroyed)
return;
}
synchronized (handles) {
handles.remove(handle);
}
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(handle);
Collection copy = null;
synchronized (cels) {
copy = new ArrayList(cels);
}
for (Iterator i = copy.iterator(); i.hasNext();) {
ConnectionEventListener cel = (ConnectionEventListener) i.next();
cel.connectionClosed(ce);
}
}
void connectionError(Throwable t) {
if (t instanceof SQLException == false || mcf.isExceptionFatal((SQLException) t))
broadcastConnectionError(t);
}
protected void broadcastConnectionError(Throwable e) {
synchronized (stateLock) {
if (destroyed) {
Logger log = getLog();
if (log.isDebugEnabled())
log.debug("Not broadcasting error, already destroyed " + this, e);
return;
}
}
Exception ex = null;
if (e instanceof Exception)
ex = (Exception) e;
else
ex = new ResourceAdapterInternalException("Unexpected error", e);
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_ERROR_OCCURRED,
ex);
Collection copy = null;
synchronized (cels) {
copy = new ArrayList(cels);
}
for (Iterator i = copy.iterator(); i.hasNext();) {
ConnectionEventListener cel = (ConnectionEventListener) i.next();
try {
cel.connectionErrorOccurred(ce);
} catch (Throwable t) {
getLog().warn("Error notifying of connection error for listener: " + cel, t);
}
}
}
Connection getConnection() throws SQLException {
if (con == null)
throw new SQLException("Connection has been destroyed!!!");
return con;
}
PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
if (psCache != null) {
PreparedStatementCache.Key key = new PreparedStatementCache.Key(sql,
PreparedStatementCache.Key.PREPARED_STATEMENT, resultSetType, resultSetConcurrency);
CachedPreparedStatement cachedps = (CachedPreparedStatement) psCache.get(key);
if (cachedps != null) {
if (canUse(cachedps))
cachedps.inUse();
else
return doPrepareStatement(sql, resultSetType, resultSetConcurrency);
} else {
PreparedStatement ps = doPrepareStatement(sql, resultSetType, resultSetConcurrency);
cachedps = new CachedPreparedStatement(ps);
psCache.insert(key, cachedps);
}
return cachedps;
} else
return doPrepareStatement(sql, resultSetType, resultSetConcurrency);
}
PreparedStatement doPrepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return con.prepareStatement(sql, resultSetType, resultSetConcurrency);
}
CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
if (psCache != null) {
PreparedStatementCache.Key key = new PreparedStatementCache.Key(sql,
PreparedStatementCache.Key.CALLABLE_STATEMENT, resultSetType, resultSetConcurrency);
CachedCallableStatement cachedps = (CachedCallableStatement) psCache.get(key);
if (cachedps != null) {
if (canUse(cachedps))
cachedps.inUse();
else
return doPrepareCall(sql, resultSetType, resultSetConcurrency);
} else {
CallableStatement cs = doPrepareCall(sql, resultSetType, resultSetConcurrency);
cachedps = new CachedCallableStatement(cs);
psCache.insert(key, cachedps);
}
return cachedps;
} else
return doPrepareCall(sql, resultSetType, resultSetConcurrency);
}
CallableStatement doPrepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return con.prepareCall(sql, resultSetType, resultSetConcurrency);
}
boolean canUse(CachedPreparedStatement cachedps) {
// Nobody is using it so we are ok
if (cachedps.isInUse() == false)
return true;
// Cannot reuse prepared statements in auto commit mode
// if will close the previous usage of the PS
if (underlyingAutoCommit == true)
return false;
// We have been told not to share
return mcf.sharePS;
}
protected Logger getLog() {
return mcf.log;
}
private void checkIdentity(Subject subject, ConnectionRequestInfo cri) throws ResourceException {
Properties newProps = mcf.getConnectionProperties(subject, cri);
if (!props.equals(newProps)) {
throw new JBossResourceException("Wrong credentials passed to getConnection!");
} // end of if ()
}
/**
* The <code>checkTransaction</code> method makes sure the adapter follows the JCA
* autocommit contract, namely all statements executed outside a container managed transaction
* or a component managed transaction should be autocommitted. To avoid continually calling
* setAutocommit(enable) before and after container managed transactions, we keep track of the state
* and check it before each transactional method call.
*/
void checkTransaction() throws SQLException {
synchronized (stateLock) {
if (inManagedTransaction)
return;
// Check autocommit
if (jdbcAutoCommit != underlyingAutoCommit) {
con.setAutoCommit(jdbcAutoCommit);
underlyingAutoCommit = jdbcAutoCommit;
}
}
if (jdbcAutoCommit == false && inLocalTransaction.getAndSet(true) == false) {
ArrayList copy;
synchronized (cels) {
copy = new ArrayList(cels);
}
ConnectionEvent ce = new ConnectionEvent(this,
ConnectionEvent.LOCAL_TRANSACTION_STARTED);
for (int i = 0; i < copy.size(); ++i) {
ConnectionEventListener cel = (ConnectionEventListener) copy.get(i);
try {
cel.localTransactionStarted(ce);
} catch (Throwable t) {
getLog().error("Error notifying of connection committed for listener: " + cel,
t);
}
}
}
checkState();
}
protected void checkState() throws SQLException {
synchronized (stateLock) {
// Check readonly
if (jdbcReadOnly != underlyingReadOnly) {
con.setReadOnly(jdbcReadOnly);
underlyingReadOnly = jdbcReadOnly;
}
}
}
boolean isJdbcAutoCommit() {
return inManagedTransaction ? false : jdbcAutoCommit;
}
void setJdbcAutoCommit(final boolean jdbcAutoCommit) throws SQLException {
synchronized (stateLock) {
if (inManagedTransaction)
throw new SQLException("You cannot set autocommit during a managed transaction!");
this.jdbcAutoCommit = jdbcAutoCommit;
}
if (jdbcAutoCommit && inLocalTransaction.getAndSet(false)) {
ArrayList copy;
synchronized (cels) {
copy = new ArrayList(cels);
}
ConnectionEvent ce = new ConnectionEvent(this,
ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);
for (int i = 0; i < copy.size(); ++i) {
ConnectionEventListener cel = (ConnectionEventListener) copy.get(i);
try {
cel.localTransactionCommitted(ce);
} catch (Throwable t) {
getLog().error("Error notifying of connection committed for listener: " + cel,
t);
}
}
}
}
boolean isJdbcReadOnly() {
return jdbcReadOnly;
}
void setJdbcReadOnly(final boolean readOnly) throws SQLException {
synchronized (stateLock) {
if (inManagedTransaction)
throw new SQLException("You cannot set read only during a managed transaction!");
this.jdbcReadOnly = readOnly;
}
}
int getJdbcTransactionIsolation() {
return jdbcTransactionIsolation;
}
void setJdbcTransactionIsolation(final int isolationLevel) throws SQLException {
synchronized (stateLock) {
this.jdbcTransactionIsolation = isolationLevel;
con.setTransactionIsolation(jdbcTransactionIsolation);
}
}
void jdbcCommit() throws SQLException {
synchronized (stateLock) {
if (inManagedTransaction)
throw new SQLException("You cannot commit during a managed transaction!");
if (jdbcAutoCommit)
throw new SQLException("You cannot commit with autocommit set!");
}
con.commit();
if (inLocalTransaction.getAndSet(false)) {
ArrayList copy;
synchronized (cels) {
copy = new ArrayList(cels);
}
ConnectionEvent ce = new ConnectionEvent(this,
ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);
for (int i = 0; i < copy.size(); ++i) {
ConnectionEventListener cel = (ConnectionEventListener) copy.get(i);
try {
cel.localTransactionCommitted(ce);
} catch (Throwable t) {
getLog().error("Error notifying of connection committed for listener: " + cel,
t);
}
}
}
}
void jdbcRollback() throws SQLException {
synchronized (stateLock) {
if (inManagedTransaction)
throw new SQLException("You cannot rollback during a managed transaction!");
if (jdbcAutoCommit)
throw new SQLException("You cannot rollback with autocommit set!");
}
con.rollback();
if (inLocalTransaction.getAndSet(false)) {
ArrayList copy;
synchronized (cels) {
copy = new ArrayList(cels);
}
ConnectionEvent ce = new ConnectionEvent(this,
ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK);
for (int i = 0; i < copy.size(); ++i) {
ConnectionEventListener cel = (ConnectionEventListener) copy.get(i);
try {
cel.localTransactionRolledback(ce);
} catch (Throwable t) {
getLog()
.error("Error notifying of connection rollback for listener: " + cel, t);
}
}
}
}
void jdbcRollback(Savepoint savepoint) throws SQLException {
synchronized (stateLock) {
if (inManagedTransaction)
throw new SQLException("You cannot rollback during a managed transaction!");
if (jdbcAutoCommit)
throw new SQLException("You cannot rollback with autocommit set!");
}
con.rollback(savepoint);
}
int getTrackStatements() {
return mcf.trackStatements;
}
boolean isTransactionQueryTimeout() {
return mcf.isTransactionQueryTimeout;
}
int getQueryTimeout() {
return mcf.getQueryTimeout();
}
protected void checkException(SQLException e) throws ResourceException {
connectionError(e);
throw new JBossResourceException("SQLException", e);
}
}