1 /*
2 * XAPool: Open Source XA JDBC Pool
3 * Copyright (C) 2003 Objectweb.org
4 * Initial Developer: Lutris Technologies Inc.
5 * Contact: xapool-public@lists.debian-sf.objectweb.org
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 * USA
21 */
22 package org.enhydra.jdbc.standard;
23
24 import java.sql.CallableStatement;
25 import java.sql.PreparedStatement;
26 import java.sql.SQLException;
27 import java.util.Enumeration;
28 import java.util.Hashtable;
29 import java.sql.Connection;
30 import org.enhydra.jdbc.core.CoreConnection;
31 import org.enhydra.jdbc.util.LRUCache;
32
33 /**
34 * This is an implementation of java.sql.Connection which simply
35 * delegates almost everything to an underlying physical implemention
36 * of the same interface.
37 *
38 * It relies on a StandardPooledConnection to create it and to supply the
39 * physical connection and a cache of PreparedStatements. This class will
40 * try to re-use PreparedStatements wherever possible and will add to the
41 * cache when totally new PreparedStatements get created.
42 */
43 public class StandardConnectionHandle extends CoreConnection {
44
45 StandardPooledConnection pooledCon;
46 // the pooled connection that created this object
47 protected Hashtable masterPrepStmtCache;
48 // the hashtable of caches, indexed by physical connection
49 int preparedStmtCacheSize; // the size of the connection-specific cache
50 protected LRUCache preparedStatementCache = null;
51 // prepared statements indexed by SQL string
52 public Hashtable inUse; // prepared statements that are currently in use
53 private boolean closed; // set true when this connection has been closed
54 public boolean isReallyUsed = false;
55
56 /**
57 * Constructor.
58 */
59 public StandardConnectionHandle(
60 StandardPooledConnection pooledCon,
61 Hashtable preparedStatementCache,
62 int preparedStmtCacheSize) {
63 super(pooledCon.getPhysicalConnection()); // get the real connection
64 this.pooledCon = pooledCon; // first save parameters
65 masterPrepStmtCache = preparedStatementCache;
66 this.preparedStmtCacheSize = preparedStmtCacheSize;
67 log = pooledCon.dataSource.log;
68 setupPreparedStatementCache();
69 inUse = new Hashtable(10, 0.5f);
70
71 log.debug(
72 "StandardConnectionHandle:new StandardConnectionHandle with "
73 + preparedStmtCacheSize
74 + " prepared statement");
75 }
76
77 protected void setupPreparedStatementCache() {
78 log.debug("StandardConnectionHandle:setupPreparedStatementCache start");
79 if (preparedStmtCacheSize == 0) {
80 log.debug(
81 "StandardConnectionHandle:setupPreparedStatementCache return with 0");
82 preparedStatementCache = null;
83 return;
84 }
85 if (con == null)
86 log.warn("Connection is null");
87 else {
88 preparedStatementCache =
89 (LRUCache) masterPrepStmtCache.get(con.toString());
90 if (preparedStatementCache == null) {
91 preparedStatementCache =
92 new PreparedStatementCache(preparedStmtCacheSize);
93 preparedStatementCache.setLogger(log);
94 masterPrepStmtCache.put(con.toString(), preparedStatementCache);
95 log.debug(
96 "StandardConnectionHandle:setupPreparedStatementCache "
97 + "preparedStatementCache.size(lru)='"
98 + preparedStatementCache.LRUSize()
99 + "' "
100 + "preparedStatementCache.size(cache)='"
101 + preparedStatementCache.cacheSize()
102 + "' "
103 + "masterPrepStmtCache.size='"
104 + masterPrepStmtCache.size()
105 + "' ");
106 } else preparedStatementCache.setLogger(log);
107 }
108 log.debug("StandardConnectionHandle:setupPreparedStatementCache end");
109 }
110
111 /**
112 * Pre-invokation of the delegation, in case of connection is
113 * closed, we throw an exception
114 */
115 public void preInvoke() throws SQLException {
116 if (closed)
117 throw new SQLException("Connection is closed");
118 }
119
120 /**
121 * Exception management : catch or throw the exception
122 */
123 public void catchInvoke(SQLException e) throws SQLException {
124 //ConnectionEvent event = new ConnectionEvent (pooledCon);// create event associate with the connection
125 //pooledCon.connectionErrorOccurred(event); // ppoled have to be closed
126 throw (e); // throw the exception
127 }
128
129 /**
130 * Closes this StandardConnectionHandle and prevents it
131 * from being reused. It also returns used PreparedStatements
132 * to the PreparedStatement cache and notifies all listeners.
133 */
134 synchronized public void close() throws SQLException {
135 log.debug("StandardConnectionHandle:close");
136 // Note - we don't check to see if already closed. Some servers get confused.
137 closed = true; // connection now closed
138 Enumeration keys = inUse.keys(); // get any prepared statements in use
139 while (keys.hasMoreElements()) { // while more prepared statements used
140 Object key = keys.nextElement(); // get next key
141 returnToCache(key); // return prepared statement to cache
142 }
143 pooledCon.closeEvent(); // notify listeners
144
145 if (preparedStatementCache != null)
146 preparedStatementCache.cleanupAll();
147 if ((preparedStatementCache != null) && (masterPrepStmtCache != null) && (log != null))
148 log.debug(
149 "StandardConnectionHandle:close "
150 + "preparedStatementCache.size(lru)='"
151 + preparedStatementCache.LRUSize()
152 + "' "
153 + "preparedStatementCache.size(cache)='"
154 + preparedStatementCache.cacheSize()
155 + "' "
156 + "masterPrepStmtCache.size='"
157 + masterPrepStmtCache.size()
158 + "' ");
159 }
160
161 /**
162 * Removes a prepared statement from the inUse list
163 * and returns it to the cache.
164 */
165 void returnToCache(Object key, Connection theCon) {
166 Object value = inUse.remove(key);
167 // remove key/value from used statements
168 if (value != null) {
169 LRUCache theCache =
170 (LRUCache) masterPrepStmtCache.get(theCon.toString());
171 theCache.put(key, value); // place back in cache, ready for re-use
172 }
173 }
174
175 void returnToCache(Object key) {
176 returnToCache(key, con);
177 }
178
179 /**
180 * Checks to see if a prepared statement with the same concurrency
181 * has already been created. If not, then a new prepared statement
182 * is created and added to the cache.
183 *
184 * If a prepared statement is found in the cache then it is removed
185 * from the cache and placed on the "inUse" list. This ensures that
186 * if multiple threads use the same StandardConnectionHandle, or a single
187 * thread does multiple prepares using the same SQL, then DIFFERENT
188 * prepared statements will be returned.
189 */
190 synchronized PreparedStatement checkPreparedCache(
191 String sql,
192 int type,
193 int concurrency,
194 int holdability)
195 throws SQLException {
196 log.debug(
197 "StandardConnectionHandle:checkPreparedCache sql='" + sql + "'");
198 PreparedStatement ret = null; // the return value
199 // NOTE - We include the Connection in the lookup key. This has no
200 // effect here but is needed by StandardXAConnection where the the physical
201 // Connection used can vary over time depending on the global transaction.
202 String lookupKey = sql + type + concurrency;
203 // used to lookup statements
204 if (preparedStatementCache != null) {
205 Object obj = preparedStatementCache.get(lookupKey);
206 // see if there's a PreparedStatement already
207 if (obj != null) { // if there is
208 ret = (PreparedStatement) obj; // use as return value
209 try {
210 ret.clearParameters(); // make it look like new
211 } catch (SQLException e) {
212 // Bad statement, so we have to create a new one
213 ret = createPreparedStatement(sql, type, concurrency, holdability);
214 }
215
216 preparedStatementCache.remove(lookupKey);
217 // make sure it cannot be re-used
218 inUse.put(lookupKey, ret);
219 // make sure it gets reused by later delegates
220 } else { // no PreparedStatement ready
221 ret = createPreparedStatement(sql, type, concurrency, holdability);
222 inUse.put(lookupKey, ret);
223 // will get saved in prepared statement cache
224 }
225 } else {
226 ret = createPreparedStatement(sql, type, concurrency, holdability);
227 }
228 // We don't actually give the application a real PreparedStatement. Instead
229 // they get a StandardPreparedStatement that delegates everything except
230 // PreparedStatement.close();
231
232 ret = new StandardPreparedStatement(this, ret, lookupKey);
233 return ret;
234 }
235
236
237 synchronized PreparedStatement checkPreparedCache(
238 String sql,
239 int autogeneratedkeys)
240 throws SQLException {
241 log.debug(
242 "StandardConnectionHandle:checkPreparedCache sql='" + sql + "'");
243 PreparedStatement ret = null; // the return value
244 // NOTE - We include the Connection in the lookup key. This has no
245 // effect here but is needed by StandardXAConnection where the the physical
246 // Connection used can vary over time depending on the global transaction.
247 String lookupKey = sql + autogeneratedkeys;
248 // used to lookup statements
249 if (preparedStatementCache != null) {
250 Object obj = preparedStatementCache.get(lookupKey);
251 // see if there's a PreparedStatement already
252 if (obj != null) { // if there is
253 ret = (PreparedStatement) obj; // use as return value
254 try {
255 ret.clearParameters(); // make it look like new
256 } catch (SQLException e) {
257 // Bad statement, so we have to create a new one
258 ret = createPreparedStatement(sql, autogeneratedkeys);
259 }
260
261 preparedStatementCache.remove(lookupKey);
262 // make sure it cannot be re-used
263 inUse.put(lookupKey, ret);
264 // make sure it gets reused by later delegates
265 } else { // no PreparedStatement ready
266 ret = createPreparedStatement(sql, autogeneratedkeys);
267 inUse.put(lookupKey, ret);
268 // will get saved in prepared statement cache
269 }
270 } else {
271 ret = createPreparedStatement(sql, autogeneratedkeys);
272 }
273 // We don't actually give the application a real PreparedStatement. Instead
274 // they get a StandardPreparedStatement that delegates everything except
275 // PreparedStatement.close();
276
277 ret = new StandardPreparedStatement(this, ret, lookupKey);
278 return ret;
279 }
280
281
282
283 protected PreparedStatement createPreparedStatement(
284 String sql,
285 int type,
286 int concurrency,
287 int holdability)
288 throws SQLException {
289 log.debug(
290 "StandardConnectionHandle:createPreparedStatement type ='"
291 + type
292 + "'");
293 if (type == 0 && holdability == 0) { // if no type or concurrency specified
294 return con.prepareStatement(sql); // create new prepared statement
295 } else if (holdability == 0) {
296 return con.prepareStatement(sql, type, concurrency);
297 // create new prepared statement
298 } else return con.prepareStatement(sql, type, concurrency, holdability);
299 }
300
301
302 protected PreparedStatement createPreparedStatement(
303 String sql,
304 int autogeneratedkeys)
305 throws SQLException {
306 log.debug(
307 "StandardConnectionHandle:createPreparedStatement autogeneratedkeys ='"
308 + autogeneratedkeys
309 + "'");
310 return con.prepareStatement(sql, autogeneratedkeys); // create new prepared statement
311 }
312
313 /**
314 * Creates a PreparedStatement for the given SQL. If possible, the
315 * statement is fetched from the cache.
316 */
317 public PreparedStatement prepareStatement(String sql) throws SQLException {
318 log.debug(
319 "StandardConnectionHandle:prepareStatement sql='" + sql + "'");
320 preInvoke();
321 try {
322 return checkPreparedCache(sql, 0, 0, 0);
323 } catch (SQLException e) {
324 catchInvoke(e);
325 }
326 return null;
327 }
328
329 /**
330 * Creates a PreparedStatement for the given SQL, type and concurrency.
331 * If possible, the statement is fetched from the cache.
332 */
333 public PreparedStatement prepareStatement(
334 String sql,
335 int resultSetType,
336 int resultSetConcurrency)
337 throws SQLException {
338 preInvoke();
339 try {
340 return checkPreparedCache(sql, resultSetType, resultSetConcurrency, 0);
341 } catch (SQLException e) {
342 catchInvoke(e);
343 }
344 return null;
345 }
346
347 public PreparedStatement prepareStatement(
348 String sql,
349 int resultSetType,
350 int resultSetConcurrency,
351 int resultSetHoldability)
352 throws SQLException {
353 preInvoke();
354 try {
355 return checkPreparedCache(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
356 } catch (SQLException e) {
357 catchInvoke(e);
358 }
359 return null;
360 }
361
362 public boolean isClosed() throws SQLException {
363 return closed;
364 }
365
366 public CallableStatement prepareCall(
367 String sql,
368 int resultSetType,
369 int resultSetConcurrency)
370 throws SQLException {
371 preInvoke();
372 try {
373 return con.prepareCall(sql, resultSetType, resultSetConcurrency);
374 } catch (SQLException e) {
375 catchInvoke(e);
376 }
377 return null;
378 }
379 }