/*
* XAPool: Open Source XA JDBC Pool
* Copyright (C) 2003 Objectweb.org
* Initial Developer: Lutris Technologies Inc.
* Contact: xapool-public@lists.debian-sf.objectweb.org
*
* This library 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 any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package org.enhydra.jdbc.standard;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.sql.Connection;
import org.enhydra.jdbc.core.CoreConnection;
import org.enhydra.jdbc.util.LRUCache;
This is an implementation of java.sql.Connection which simply
delegates almost everything to an underlying physical implemention
of the same interface.
It relies on a StandardPooledConnection to create it and to supply the
physical connection and a cache of PreparedStatements. This class will
try to re-use PreparedStatements wherever possible and will add to the
cache when totally new PreparedStatements get created.
/**
* This is an implementation of java.sql.Connection which simply
* delegates almost everything to an underlying physical implemention
* of the same interface.
*
* It relies on a StandardPooledConnection to create it and to supply the
* physical connection and a cache of PreparedStatements. This class will
* try to re-use PreparedStatements wherever possible and will add to the
* cache when totally new PreparedStatements get created.
*/
public class StandardConnectionHandle extends CoreConnection {
StandardPooledConnection pooledCon;
// the pooled connection that created this object
protected Hashtable masterPrepStmtCache;
// the hashtable of caches, indexed by physical connection
int preparedStmtCacheSize; // the size of the connection-specific cache
protected LRUCache preparedStatementCache = null;
// prepared statements indexed by SQL string
public Hashtable inUse; // prepared statements that are currently in use
private boolean closed; // set true when this connection has been closed
public boolean isReallyUsed = false;
Constructor.
/**
* Constructor.
*/
public StandardConnectionHandle(
StandardPooledConnection pooledCon,
Hashtable preparedStatementCache,
int preparedStmtCacheSize) {
super(pooledCon.getPhysicalConnection()); // get the real connection
this.pooledCon = pooledCon; // first save parameters
masterPrepStmtCache = preparedStatementCache;
this.preparedStmtCacheSize = preparedStmtCacheSize;
log = pooledCon.dataSource.log;
setupPreparedStatementCache();
inUse = new Hashtable(10, 0.5f);
log.debug(
"StandardConnectionHandle:new StandardConnectionHandle with "
+ preparedStmtCacheSize
+ " prepared statement");
}
protected void setupPreparedStatementCache() {
log.debug("StandardConnectionHandle:setupPreparedStatementCache start");
if (preparedStmtCacheSize == 0) {
log.debug(
"StandardConnectionHandle:setupPreparedStatementCache return with 0");
preparedStatementCache = null;
return;
}
if (con == null)
log.warn("Connection is null");
else {
preparedStatementCache =
(LRUCache) masterPrepStmtCache.get(con.toString());
if (preparedStatementCache == null) {
preparedStatementCache =
new PreparedStatementCache(preparedStmtCacheSize);
preparedStatementCache.setLogger(log);
masterPrepStmtCache.put(con.toString(), preparedStatementCache);
log.debug(
"StandardConnectionHandle:setupPreparedStatementCache "
+ "preparedStatementCache.size(lru)='"
+ preparedStatementCache.LRUSize()
+ "' "
+ "preparedStatementCache.size(cache)='"
+ preparedStatementCache.cacheSize()
+ "' "
+ "masterPrepStmtCache.size='"
+ masterPrepStmtCache.size()
+ "' ");
} else preparedStatementCache.setLogger(log);
}
log.debug("StandardConnectionHandle:setupPreparedStatementCache end");
}
Pre-invokation of the delegation, in case of connection is
closed, we throw an exception
/**
* Pre-invokation of the delegation, in case of connection is
* closed, we throw an exception
*/
public void preInvoke() throws SQLException {
if (closed)
throw new SQLException("Connection is closed");
}
Exception management : catch or throw the exception
/**
* Exception management : catch or throw the exception
*/
public void catchInvoke(SQLException e) throws SQLException {
//ConnectionEvent event = new ConnectionEvent (pooledCon);// create event associate with the connection
//pooledCon.connectionErrorOccurred(event); // ppoled have to be closed
throw (e); // throw the exception
}
Closes this StandardConnectionHandle and prevents it
from being reused. It also returns used PreparedStatements
to the PreparedStatement cache and notifies all listeners.
/**
* Closes this StandardConnectionHandle and prevents it
* from being reused. It also returns used PreparedStatements
* to the PreparedStatement cache and notifies all listeners.
*/
synchronized public void close() throws SQLException {
log.debug("StandardConnectionHandle:close");
// Note - we don't check to see if already closed. Some servers get confused.
closed = true; // connection now closed
Enumeration keys = inUse.keys(); // get any prepared statements in use
while (keys.hasMoreElements()) { // while more prepared statements used
Object key = keys.nextElement(); // get next key
returnToCache(key); // return prepared statement to cache
}
pooledCon.closeEvent(); // notify listeners
if (preparedStatementCache != null)
preparedStatementCache.cleanupAll();
if ((preparedStatementCache != null) && (masterPrepStmtCache != null) && (log != null))
log.debug(
"StandardConnectionHandle:close "
+ "preparedStatementCache.size(lru)='"
+ preparedStatementCache.LRUSize()
+ "' "
+ "preparedStatementCache.size(cache)='"
+ preparedStatementCache.cacheSize()
+ "' "
+ "masterPrepStmtCache.size='"
+ masterPrepStmtCache.size()
+ "' ");
}
Removes a prepared statement from the inUse list
and returns it to the cache.
/**
* Removes a prepared statement from the inUse list
* and returns it to the cache.
*/
void returnToCache(Object key, Connection theCon) {
Object value = inUse.remove(key);
// remove key/value from used statements
if (value != null) {
LRUCache theCache =
(LRUCache) masterPrepStmtCache.get(theCon.toString());
theCache.put(key, value); // place back in cache, ready for re-use
}
}
void returnToCache(Object key) {
returnToCache(key, con);
}
Checks to see if a prepared statement with the same concurrency
has already been created. If not, then a new prepared statement
is created and added to the cache.
If a prepared statement is found in the cache then it is removed
from the cache and placed on the "inUse" list. This ensures that
if multiple threads use the same StandardConnectionHandle, or a single
thread does multiple prepares using the same SQL, then DIFFERENT
prepared statements will be returned.
/**
* Checks to see if a prepared statement with the same concurrency
* has already been created. If not, then a new prepared statement
* is created and added to the cache.
*
* If a prepared statement is found in the cache then it is removed
* from the cache and placed on the "inUse" list. This ensures that
* if multiple threads use the same StandardConnectionHandle, or a single
* thread does multiple prepares using the same SQL, then DIFFERENT
* prepared statements will be returned.
*/
synchronized PreparedStatement checkPreparedCache(
String sql,
int type,
int concurrency,
int holdability)
throws SQLException {
log.debug(
"StandardConnectionHandle:checkPreparedCache sql='" + sql + "'");
PreparedStatement ret = null; // the return value
// NOTE - We include the Connection in the lookup key. This has no
// effect here but is needed by StandardXAConnection where the the physical
// Connection used can vary over time depending on the global transaction.
String lookupKey = sql + type + concurrency;
// used to lookup statements
if (preparedStatementCache != null) {
Object obj = preparedStatementCache.get(lookupKey);
// see if there's a PreparedStatement already
if (obj != null) { // if there is
ret = (PreparedStatement) obj; // use as return value
try {
ret.clearParameters(); // make it look like new
} catch (SQLException e) {
// Bad statement, so we have to create a new one
ret = createPreparedStatement(sql, type, concurrency, holdability);
}
preparedStatementCache.remove(lookupKey);
// make sure it cannot be re-used
inUse.put(lookupKey, ret);
// make sure it gets reused by later delegates
} else { // no PreparedStatement ready
ret = createPreparedStatement(sql, type, concurrency, holdability);
inUse.put(lookupKey, ret);
// will get saved in prepared statement cache
}
} else {
ret = createPreparedStatement(sql, type, concurrency, holdability);
}
// We don't actually give the application a real PreparedStatement. Instead
// they get a StandardPreparedStatement that delegates everything except
// PreparedStatement.close();
ret = new StandardPreparedStatement(this, ret, lookupKey);
return ret;
}
synchronized PreparedStatement checkPreparedCache(
String sql,
int autogeneratedkeys)
throws SQLException {
log.debug(
"StandardConnectionHandle:checkPreparedCache sql='" + sql + "'");
PreparedStatement ret = null; // the return value
// NOTE - We include the Connection in the lookup key. This has no
// effect here but is needed by StandardXAConnection where the the physical
// Connection used can vary over time depending on the global transaction.
String lookupKey = sql + autogeneratedkeys;
// used to lookup statements
if (preparedStatementCache != null) {
Object obj = preparedStatementCache.get(lookupKey);
// see if there's a PreparedStatement already
if (obj != null) { // if there is
ret = (PreparedStatement) obj; // use as return value
try {
ret.clearParameters(); // make it look like new
} catch (SQLException e) {
// Bad statement, so we have to create a new one
ret = createPreparedStatement(sql, autogeneratedkeys);
}
preparedStatementCache.remove(lookupKey);
// make sure it cannot be re-used
inUse.put(lookupKey, ret);
// make sure it gets reused by later delegates
} else { // no PreparedStatement ready
ret = createPreparedStatement(sql, autogeneratedkeys);
inUse.put(lookupKey, ret);
// will get saved in prepared statement cache
}
} else {
ret = createPreparedStatement(sql, autogeneratedkeys);
}
// We don't actually give the application a real PreparedStatement. Instead
// they get a StandardPreparedStatement that delegates everything except
// PreparedStatement.close();
ret = new StandardPreparedStatement(this, ret, lookupKey);
return ret;
}
protected PreparedStatement createPreparedStatement(
String sql,
int type,
int concurrency,
int holdability)
throws SQLException {
log.debug(
"StandardConnectionHandle:createPreparedStatement type ='"
+ type
+ "'");
if (type == 0 && holdability == 0) { // if no type or concurrency specified
return con.prepareStatement(sql); // create new prepared statement
} else if (holdability == 0) {
return con.prepareStatement(sql, type, concurrency);
// create new prepared statement
} else return con.prepareStatement(sql, type, concurrency, holdability);
}
protected PreparedStatement createPreparedStatement(
String sql,
int autogeneratedkeys)
throws SQLException {
log.debug(
"StandardConnectionHandle:createPreparedStatement autogeneratedkeys ='"
+ autogeneratedkeys
+ "'");
return con.prepareStatement(sql, autogeneratedkeys); // create new prepared statement
}
Creates a PreparedStatement for the given SQL. If possible, the
statement is fetched from the cache.
/**
* Creates a PreparedStatement for the given SQL. If possible, the
* statement is fetched from the cache.
*/
public PreparedStatement prepareStatement(String sql) throws SQLException {
log.debug(
"StandardConnectionHandle:prepareStatement sql='" + sql + "'");
preInvoke();
try {
return checkPreparedCache(sql, 0, 0, 0);
} catch (SQLException e) {
catchInvoke(e);
}
return null;
}
Creates a PreparedStatement for the given SQL, type and concurrency.
If possible, the statement is fetched from the cache.
/**
* Creates a PreparedStatement for the given SQL, type and concurrency.
* If possible, the statement is fetched from the cache.
*/
public PreparedStatement prepareStatement(
String sql,
int resultSetType,
int resultSetConcurrency)
throws SQLException {
preInvoke();
try {
return checkPreparedCache(sql, resultSetType, resultSetConcurrency, 0);
} catch (SQLException e) {
catchInvoke(e);
}
return null;
}
public PreparedStatement prepareStatement(
String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException {
preInvoke();
try {
return checkPreparedCache(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
} catch (SQLException e) {
catchInvoke(e);
}
return null;
}
public boolean isClosed() throws SQLException {
return closed;
}
public CallableStatement prepareCall(
String sql,
int resultSetType,
int resultSetConcurrency)
throws SQLException {
preInvoke();
try {
return con.prepareCall(sql, resultSetType, resultSetConcurrency);
} catch (SQLException e) {
catchInvoke(e);
}
return null;
}
}