001: /*
002: * XAPool: Open Source XA JDBC Pool
003: * Copyright (C) 2003 Objectweb.org
004: * Initial Developer: Lutris Technologies Inc.
005: * Contact: xapool-public@lists.debian-sf.objectweb.org
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
020: * USA
021: */
022: package org.enhydra.jdbc.standard;
023:
024: import java.sql.CallableStatement;
025: import java.sql.PreparedStatement;
026: import java.sql.Statement;
027: import java.sql.SQLException;
028: import java.util.Enumeration;
029: import java.util.Hashtable;
030: import java.sql.Connection;
031: import org.enhydra.jdbc.core.CoreConnection;
032: import org.enhydra.jdbc.util.LRUCache;
033:
034: /**
035: * This is an implementation of java.sql.Connection which simply
036: * delegates almost everything to an underlying physical implemention
037: * of the same interface.
038: *
039: * It relies on a StandardPooledConnection to create it and to supply the
040: * physical connection and a cache of PreparedStatements. This class will
041: * try to re-use PreparedStatements wherever possible and will add to the
042: * cache when totally new PreparedStatements get created.
043: */
044: public class StandardConnectionHandle extends CoreConnection {
045:
046: StandardPooledConnection pooledCon;
047: // the pooled connection that created this object
048: protected Hashtable masterPrepStmtCache;
049: // the hashtable of caches, indexed by physical connection
050: int preparedStmtCacheSize; // the size of the connection-specific cache
051: protected LRUCache preparedStatementCache = null;
052: // prepared statements indexed by SQL string
053: public Hashtable inUse; // prepared statements that are currently in use
054: private boolean closed; // set true when this connection has been closed
055: public boolean isReallyUsed = false;
056:
057: /**
058: * Constructor.
059: */
060: public StandardConnectionHandle(StandardPooledConnection pooledCon,
061: Hashtable preparedStatementCache, int preparedStmtCacheSize) {
062: super (pooledCon.getPhysicalConnection()); // get the real connection
063: this .pooledCon = pooledCon; // first save parameters
064: masterPrepStmtCache = preparedStatementCache;
065: this .preparedStmtCacheSize = preparedStmtCacheSize;
066: log = pooledCon.dataSource.log;
067: setupPreparedStatementCache();
068: inUse = new Hashtable(10, 0.5f);
069:
070: log
071: .debug("StandardConnectionHandle:new StandardConnectionHandle with "
072: + preparedStmtCacheSize + " prepared statement");
073: }
074:
075: protected void setupPreparedStatementCache() {
076: log
077: .debug("StandardConnectionHandle:setupPreparedStatementCache start");
078: if (preparedStmtCacheSize == 0) {
079: log
080: .debug("StandardConnectionHandle:setupPreparedStatementCache return with 0");
081: preparedStatementCache = null;
082: return;
083: }
084: if (con == null)
085: log.warn("Connection is null");
086: else {
087: preparedStatementCache = (LRUCache) masterPrepStmtCache
088: .get(con.toString());
089: if (preparedStatementCache == null) {
090: preparedStatementCache = new PreparedStatementCache(
091: preparedStmtCacheSize);
092: preparedStatementCache.setLogger(log);
093: masterPrepStmtCache.put(con.toString(),
094: preparedStatementCache);
095: log
096: .debug("StandardConnectionHandle:setupPreparedStatementCache "
097: + "preparedStatementCache.size(lru)='"
098: + preparedStatementCache.LRUSize()
099: + "' "
100: + "preparedStatementCache.size(cache)='"
101: + preparedStatementCache.cacheSize()
102: + "' "
103: + "masterPrepStmtCache.size='"
104: + masterPrepStmtCache.size() + "' ");
105: } else
106: preparedStatementCache.setLogger(log);
107: }
108: log
109: .debug("StandardConnectionHandle:setupPreparedStatementCache end");
110: }
111:
112: /**
113: * Pre-invokation of the delegation, in case of connection is
114: * closed, we throw an exception
115: */
116: public void preInvoke() throws SQLException {
117: if (closed)
118: throw new SQLException("Connection is closed");
119: }
120:
121: /**
122: * Exception management : catch or throw the exception
123: */
124: public void catchInvoke(SQLException e) throws SQLException {
125: //ConnectionEvent event = new ConnectionEvent (pooledCon);// create event associate with the connection
126: //pooledCon.connectionErrorOccurred(event); // ppoled have to be closed
127: throw (e); // throw the exception
128: }
129:
130: /**
131: * Closes this StandardConnectionHandle and prevents it
132: * from being reused. It also returns used PreparedStatements
133: * to the PreparedStatement cache and notifies all listeners.
134: */
135: synchronized public void close() throws SQLException {
136: log.debug("StandardConnectionHandle:close");
137: // Note - we don't check to see if already closed. Some servers get confused.
138: closed = true; // connection now closed
139: Enumeration keys = inUse.keys(); // get any prepared statements in use
140: while (keys.hasMoreElements()) { // while more prepared statements used
141: Object key = keys.nextElement(); // get next key
142: returnToCache(key); // return prepared statement to cache
143: }
144: pooledCon.closeEvent(); // notify listeners
145:
146: if (preparedStatementCache != null)
147: preparedStatementCache.cleanupAll();
148: if ((preparedStatementCache != null)
149: && (masterPrepStmtCache != null) && (log != null))
150: log.debug("StandardConnectionHandle:close "
151: + "preparedStatementCache.size(lru)='"
152: + preparedStatementCache.LRUSize() + "' "
153: + "preparedStatementCache.size(cache)='"
154: + preparedStatementCache.cacheSize() + "' "
155: + "masterPrepStmtCache.size='"
156: + masterPrepStmtCache.size() + "' ");
157: }
158:
159: /**
160: * Removes a prepared statement from the inUse list
161: * and returns it to the cache.
162: */
163: void returnToCache(Object key, Connection theCon) {
164: Object value = inUse.remove(key);
165: // remove key/value from used statements
166: if (value != null) {
167: LRUCache theCache = (LRUCache) masterPrepStmtCache
168: .get(theCon.toString());
169: theCache.put(key, value); // place back in cache, ready for re-use
170: }
171: }
172:
173: void returnToCache(Object key) {
174: returnToCache(key, con);
175: }
176:
177: /**
178: * Checks to see if a prepared statement with the same concurrency
179: * has already been created. If not, then a new prepared statement
180: * is created and added to the cache.
181: *
182: * If a prepared statement is found in the cache then it is removed
183: * from the cache and placed on the "inUse" list. This ensures that
184: * if multiple threads use the same StandardConnectionHandle, or a single
185: * thread does multiple prepares using the same SQL, then DIFFERENT
186: * prepared statements will be returned.
187: */
188: synchronized PreparedStatement checkPreparedCache(String sql,
189: int type, int concurrency, int holdability)
190: throws SQLException {
191: log.debug("StandardConnectionHandle:checkPreparedCache sql='"
192: + sql + "'");
193: PreparedStatement ret = null; // the return value
194: // NOTE - We include the Connection in the lookup key. This has no
195: // effect here but is needed by StandardXAConnection where the the physical
196: // Connection used can vary over time depending on the global transaction.
197: String lookupKey = sql + type + concurrency;
198: // used to lookup statements
199: if (preparedStatementCache != null) {
200: Object obj = preparedStatementCache.get(lookupKey);
201: // see if there's a PreparedStatement already
202: if (obj != null) { // if there is
203: ret = (PreparedStatement) obj; // use as return value
204: try {
205: ret.clearParameters(); // make it look like new
206: } catch (SQLException e) {
207: // Bad statement, so we have to create a new one
208: ret = createPreparedStatement(sql, type,
209: concurrency, holdability);
210: }
211:
212: preparedStatementCache.remove(lookupKey);
213: // make sure it cannot be re-used
214: inUse.put(lookupKey, ret);
215: // make sure it gets reused by later delegates
216: } else { // no PreparedStatement ready
217: ret = createPreparedStatement(sql, type, concurrency,
218: holdability);
219: inUse.put(lookupKey, ret);
220: // will get saved in prepared statement cache
221: }
222: } else {
223: ret = createPreparedStatement(sql, type, concurrency,
224: holdability);
225: }
226: // We don't actually give the application a real PreparedStatement. Instead
227: // they get a StandardPreparedStatement that delegates everything except
228: // PreparedStatement.close();
229:
230: ret = new StandardPreparedStatement(this , ret, lookupKey);
231: return ret;
232: }
233:
234: synchronized PreparedStatement checkPreparedCache(String sql,
235: int autogeneratedkeys) throws SQLException {
236: log.debug("StandardConnectionHandle:checkPreparedCache sql='"
237: + sql + "'");
238: PreparedStatement ret = null; // the return value
239: // NOTE - We include the Connection in the lookup key. This has no
240: // effect here but is needed by StandardXAConnection where the the physical
241: // Connection used can vary over time depending on the global transaction.
242: String lookupKey = sql + autogeneratedkeys;
243: // used to lookup statements
244: if (preparedStatementCache != null) {
245: Object obj = preparedStatementCache.get(lookupKey);
246: // see if there's a PreparedStatement already
247: if (obj != null) { // if there is
248: ret = (PreparedStatement) obj; // use as return value
249: try {
250: ret.clearParameters(); // make it look like new
251: } catch (SQLException e) {
252: // Bad statement, so we have to create a new one
253: ret = createPreparedStatement(sql,
254: autogeneratedkeys);
255: }
256:
257: preparedStatementCache.remove(lookupKey);
258: // make sure it cannot be re-used
259: inUse.put(lookupKey, ret);
260: // make sure it gets reused by later delegates
261: } else { // no PreparedStatement ready
262: ret = createPreparedStatement(sql, autogeneratedkeys);
263: inUse.put(lookupKey, ret);
264: // will get saved in prepared statement cache
265: }
266: } else {
267: ret = createPreparedStatement(sql, autogeneratedkeys);
268: }
269: // We don't actually give the application a real PreparedStatement. Instead
270: // they get a StandardPreparedStatement that delegates everything except
271: // PreparedStatement.close();
272:
273: ret = new StandardPreparedStatement(this , ret, lookupKey);
274: return ret;
275: }
276:
277: protected PreparedStatement createPreparedStatement(String sql,
278: int type, int concurrency, int holdability)
279: throws SQLException {
280: log
281: .debug("StandardConnectionHandle:createPreparedStatement type ='"
282: + type + "'");
283: if (type == 0 && holdability == 0) { // if no type or concurrency specified
284: return con.prepareStatement(sql); // create new prepared statement
285: } else if (holdability == 0) {
286: return con.prepareStatement(sql, type, concurrency);
287: // create new prepared statement
288: } else
289: return con.prepareStatement(sql, type, concurrency,
290: holdability);
291: }
292:
293: protected PreparedStatement createPreparedStatement(String sql,
294: int autogeneratedkeys) throws SQLException {
295: log
296: .debug("StandardConnectionHandle:createPreparedStatement autogeneratedkeys ='"
297: + autogeneratedkeys + "'");
298: return con.prepareStatement(sql, autogeneratedkeys); // create new prepared statement
299: }
300:
301: /**
302: * Creates a PreparedStatement for the given SQL. If possible, the
303: * statement is fetched from the cache.
304: */
305: public PreparedStatement prepareStatement(String sql)
306: throws SQLException {
307: log.debug("StandardConnectionHandle:prepareStatement sql='"
308: + sql + "'");
309: preInvoke();
310: try {
311: return checkPreparedCache(sql, 0, 0, 0);
312: } catch (SQLException e) {
313: catchInvoke(e);
314: }
315: return null;
316: }
317:
318: /**
319: * Creates a PreparedStatement for the given SQL, type and concurrency.
320: * If possible, the statement is fetched from the cache.
321: */
322: public PreparedStatement prepareStatement(String sql,
323: int resultSetType, int resultSetConcurrency)
324: throws SQLException {
325: preInvoke();
326: try {
327: return checkPreparedCache(sql, resultSetType,
328: resultSetConcurrency, 0);
329: } catch (SQLException e) {
330: catchInvoke(e);
331: }
332: return null;
333: }
334:
335: public PreparedStatement prepareStatement(String sql,
336: int resultSetType, int resultSetConcurrency,
337: int resultSetHoldability) throws SQLException {
338: preInvoke();
339: try {
340: return checkPreparedCache(sql, resultSetType,
341: resultSetConcurrency, resultSetHoldability);
342: } catch (SQLException e) {
343: catchInvoke(e);
344: }
345: return null;
346: }
347:
348: public boolean isClosed() throws SQLException {
349: if (con.isClosed()) {
350: closed = true;
351: return true;
352: } else
353: return closed;
354: }
355:
356: public CallableStatement prepareCall(String sql, int resultSetType,
357: int resultSetConcurrency) throws SQLException {
358: preInvoke();
359: try {
360: return con.prepareCall(sql, resultSetType,
361: resultSetConcurrency);
362: } catch (SQLException e) {
363: catchInvoke(e);
364: }
365: return null;
366: }
367:
368: public Statement createStatementWrapper() throws SQLException {
369: return con.createStatement();
370: }
371:
372: public Statement createStatement() throws SQLException {
373: Statement st = new StandardStatement(this,
374: createStatementWrapper());
375: return st;
376: }
377:
378: }
|