001: /*
002: * Licensed under the X license (see http://www.x.org/terms.htm)
003: */
004: package org.ofbiz.minerva.pool.jdbc;
005:
006: import java.sql.CallableStatement;
007: import java.sql.Connection;
008: import java.sql.DatabaseMetaData;
009: import java.sql.PreparedStatement;
010: import java.sql.ResultSet;
011: import java.sql.SQLException;
012: import java.sql.SQLWarning;
013: import java.sql.Savepoint;
014: import java.sql.Statement;
015: import java.util.Collection;
016: import java.util.HashMap;
017: import java.util.HashSet;
018: import java.util.Iterator;
019: import java.util.Map;
020: import java.util.Vector;
021:
022: import org.ofbiz.minerva.pool.PoolEvent;
023: import org.ofbiz.minerva.pool.PoolEventListener;
024: import org.ofbiz.minerva.pool.PooledObject;
025: import org.ofbiz.minerva.pool.cache.LeastRecentlyUsedCache;
026: import org.ofbiz.minerva.pool.cache.ObjectCache;
027:
028: /**
029: * Wrapper for database connections in a pool. Handles closing appropriately.
030: * The connection is returned to the pool rather than truly closing, any
031: * outstanding statements are closed, and the connection is rolled back. This
032: * class is also used by statements, etc. to update the last used time for the
033: * connection.
034: *
035: * @author Aaron Mulder (ammulder@alumni.princeton.edu)
036: */
037: public class ConnectionInPool implements PooledObject,
038: ConnectionWrapper {
039:
040: private final static String CLOSED = "Connection has been closed!";
041: public final static int PS_CACHE_UNLIMITED = 0;
042: public final static int PS_CACHE_DISABLED = -1;
043: public final static HashMap psCaches = new HashMap();
044:
045: private Connection con;
046: private HashSet statements;
047: private Vector listeners;
048: private int preparedStatementCacheSize = 0;
049: private ObjectCache preparedStatementCache;
050:
051: /**
052: * Creates a new connection wrapper.
053: * @param con The "real" database connection to wrap.
054: */
055: public ConnectionInPool(Connection con) {
056: this .con = con;
057: preparedStatementCache = (ObjectCache) psCaches.get(con);
058: if (preparedStatementCache == null) {
059: PreparedStatementFactory factory = new PreparedStatementFactory(
060: con);
061: preparedStatementCache = new LeastRecentlyUsedCache(
062: factory, preparedStatementCacheSize);
063: psCaches.put(con, preparedStatementCache);
064: }
065: statements = new HashSet();
066: listeners = new Vector();
067: }
068:
069: /**
070: * Creates a new connection wrapper, using the specified maximum size for
071: * the prepared statement cache.
072: * @param con The "real" database connection to wrap.
073: * @param psCacheSize The size of the PreparedStatement cache.
074: * @see #PS_CACHE_UNLIMITED
075: * @see #PS_CACHE_DISABLED
076: */
077: public ConnectionInPool(Connection con, int psCacheSize) {
078: this .con = con;
079: if (psCacheSize >= 0) {
080: preparedStatementCache = (ObjectCache) psCaches.get(con);
081: if (preparedStatementCache == null) {
082: PreparedStatementFactory factory = new PreparedStatementFactory(
083: con);
084: preparedStatementCache = new LeastRecentlyUsedCache(
085: factory, preparedStatementCacheSize);
086: psCaches.put(con, preparedStatementCache);
087: }
088: }
089: setPSCacheSize(psCacheSize);
090: statements = new HashSet();
091: listeners = new Vector();
092: }
093:
094: /**
095: * Sets the number of PreparedStatements to be cached for each
096: * Connection. Your DB product may impose a limit on the number
097: * of open PreparedStatements.
098: * @see #PS_CACHE_UNLIMITED
099: * @see #PS_CACHE_DISABLED
100: */
101: public void setPSCacheSize(int maxSize) {
102: preparedStatementCacheSize = maxSize;
103: if (maxSize >= 0 && preparedStatementCache != null)
104: preparedStatementCache.setSize(maxSize);
105: }
106:
107: /**
108: * Gets the number of PreparedStatements to be cached for each
109: * Connection.
110: */
111: public int getPSCacheSize() {
112: return preparedStatementCacheSize;
113: }
114:
115: /**
116: * Adds a listener for pool events.
117: */
118: public void addPoolEventListener(PoolEventListener listener) {
119: listeners.addElement(listener);
120: }
121:
122: /**
123: * Removes a listener for pool events.
124: */
125: public void removePoolEventListener(PoolEventListener listener) {
126: listeners.remove(listener);
127: }
128:
129: /**
130: * Gets a reference to the "real" connection. This should only be used if
131: * you need to cast that to a specific type to call a proprietary method -
132: * you will defeat all the pooling if you use the underlying connection
133: * directly.
134: */
135: public Connection getUnderlyingConnection() {
136: return con;
137: }
138:
139: /**
140: * Closes this connection wrapper permanently. All further calls with throw
141: * a SQLException.
142: */
143: public void shutdown() {
144: con = null;
145: statements = null;
146: listeners = null;
147: }
148:
149: /**
150: * Updates the last used time for this connection to the current time.
151: */
152: public void setLastUsed() {
153: firePoolEvent(new PoolEvent(this , PoolEvent.OBJECT_USED));
154: }
155:
156: /**
157: * Indicates that an error occured on this connection.
158: */
159: public void setError(SQLException e) {
160: firePoolEvent(new PoolEvent(this , PoolEvent.OBJECT_ERROR));
161: }
162:
163: /**
164: * Indicates that an error occured on this connection.
165: */
166: public void setCatastrophicError(SQLException e) {
167: PoolEvent pe = new PoolEvent(this , PoolEvent.OBJECT_ERROR);
168: pe.setCatastrophic();
169: firePoolEvent(pe);
170: }
171:
172: /**
173: * Indicates that a statement has been closed and no longer needs to be
174: * tracked. Outstanding statements are closed when the connection is
175: * returned to the pool.
176: */
177: public void statementClosed(Statement st) {
178: statements.remove(st);
179: if ((con != null) && (st instanceof PreparedStatementInPool)) {
180: // Now return the "real" statement to the pool
181: PreparedStatementInPool ps = (PreparedStatementInPool) st;
182: PreparedStatement ups = ps.getUnderlyingPreparedStatement();
183: if (preparedStatementCacheSize >= 0) {
184: preparedStatementCache.returnObject(ps.getSql(), ups);
185: } else {
186: try {
187: ups.close();
188: } catch (SQLException e) {
189: }
190: }
191: /*
192: int rsType = ResultSet.TYPE_FORWARD_ONLY;
193: int rsConcur = ResultSet.CONCUR_READ_ONLY;
194:
195: // We may have JDBC 1.0 driver
196: try {
197: rsType = ups.getResultSetType();
198: rsConcur = ups.getResultSetConcurrency();
199: } catch (Throwable th) {
200: }
201: PreparedStatementInPool.preparedStatementCache.put(
202: new PSCacheKey(con, ps.getSql(), rsType, rsConcur), ups);
203: */
204: }
205: }
206:
207: /**
208: * Prepares a connection to be returned to the pool. All outstanding
209: * statements are closed, and if AutoCommit is off, the connection is
210: * rolled back. No further SQL calls are possible once this is called.
211: */
212: public void reset() throws SQLException {
213: Collection copy = (Collection) statements.clone();
214: Iterator it = copy.iterator();
215: while (it.hasNext())
216: try {
217: ((Statement) it.next()).close();
218: } catch (SQLException e) {
219: }
220: if (!con.getAutoCommit())
221: con.rollback();
222: con = null;
223: }
224:
225: /**
226: * Dispatches an event to the listeners.
227: */
228: protected void firePoolEvent(PoolEvent evt) {
229: Vector local = (Vector) listeners.clone();
230: for (int i = local.size() - 1; i >= 0; i--)
231: if (evt.getType() == PoolEvent.OBJECT_CLOSED)
232: ((PoolEventListener) local.elementAt(i))
233: .objectClosed(evt);
234: else if (evt.getType() == PoolEvent.OBJECT_ERROR)
235: ((PoolEventListener) local.elementAt(i))
236: .objectError(evt);
237: else
238: ((PoolEventListener) local.elementAt(i))
239: .objectUsed(evt);
240: }
241:
242: // ---- Implementation of java.sql.Connection ----
243: public Statement createStatement() throws SQLException {
244: if (con == null)
245: throw new SQLException(CLOSED);
246: try {
247: StatementInPool st = new StatementInPool(con
248: .createStatement(), this );
249: statements.add(st);
250: return st;
251: } catch (SQLException e) {
252: setError(e);
253: throw e;
254: }
255: }
256:
257: public PreparedStatement prepareStatement(String sql)
258: throws SQLException {
259: return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
260: ResultSet.CONCUR_READ_ONLY);
261: }
262:
263: public CallableStatement prepareCall(String sql)
264: throws SQLException {
265: if (con == null)
266: throw new SQLException(CLOSED);
267: try {
268: return con.prepareCall(sql);
269: } catch (SQLException e) {
270: setError(e);
271: throw e;
272: }
273: }
274:
275: public String nativeSQL(String sql) throws SQLException {
276: if (con == null)
277: throw new SQLException(CLOSED);
278: try {
279: return con.nativeSQL(sql);
280: } catch (SQLException e) {
281: setError(e);
282: throw e;
283: }
284: }
285:
286: public void setAutoCommit(boolean autoCommit) throws SQLException {
287: if (con == null)
288: throw new SQLException(CLOSED);
289: try {
290: con.setAutoCommit(autoCommit);
291: } catch (SQLException e) {
292: setError(e);
293: throw e;
294: }
295: }
296:
297: public boolean getAutoCommit() throws SQLException {
298: if (con == null)
299: throw new SQLException(CLOSED);
300: try {
301: return con.getAutoCommit();
302: } catch (SQLException e) {
303: setError(e);
304: throw e;
305: }
306: }
307:
308: public void commit() throws SQLException {
309: if (con == null)
310: throw new SQLException(CLOSED);
311: try {
312: con.commit();
313: } catch (SQLException e) {
314: setCatastrophicError(e);
315: throw e;
316: }
317: }
318:
319: public void rollback() throws SQLException {
320: if (con == null)
321: throw new SQLException(CLOSED);
322: try {
323: con.rollback();
324: } catch (SQLException e) {
325: setCatastrophicError(e);
326: throw e;
327: }
328: }
329:
330: public void close() throws SQLException {
331: if (con == null)
332: throw new SQLException(CLOSED);
333: firePoolEvent(new PoolEvent(this , PoolEvent.OBJECT_CLOSED));
334: shutdown();
335: }
336:
337: public boolean isClosed() throws SQLException {
338: if (con == null)
339: return true;
340: try {
341: return con.isClosed();
342: } catch (SQLException e) {
343: setError(e);
344: throw e;
345: }
346: }
347:
348: public DatabaseMetaData getMetaData() throws SQLException {
349: if (con == null)
350: throw new SQLException(CLOSED);
351: try {
352: return con.getMetaData();
353: } catch (SQLException e) {
354: setError(e);
355: throw e;
356: }
357: }
358:
359: public void setReadOnly(boolean readOnly) throws SQLException {
360: if (con == null)
361: throw new SQLException(CLOSED);
362: try {
363: con.setReadOnly(readOnly);
364: } catch (SQLException e) {
365: setError(e);
366: throw e;
367: }
368: }
369:
370: public boolean isReadOnly() throws SQLException {
371: if (con == null)
372: throw new SQLException(CLOSED);
373: try {
374: return con.isReadOnly();
375: } catch (SQLException e) {
376: setError(e);
377: throw e;
378: }
379: }
380:
381: public void setCatalog(String catalog) throws SQLException {
382: if (con == null)
383: throw new SQLException(CLOSED);
384: try {
385: con.setCatalog(catalog);
386: } catch (SQLException e) {
387: setError(e);
388: throw e;
389: }
390: }
391:
392: public String getCatalog() throws SQLException {
393: if (con == null)
394: throw new SQLException(CLOSED);
395: try {
396: return con.getCatalog();
397: } catch (SQLException e) {
398: setError(e);
399: throw e;
400: }
401: }
402:
403: public void setTransactionIsolation(int level) throws SQLException {
404: if (con == null)
405: throw new SQLException(CLOSED);
406: try {
407: con.setTransactionIsolation(level);
408: } catch (SQLException e) {
409: setError(e);
410: throw e;
411: }
412: }
413:
414: public int getTransactionIsolation() throws SQLException {
415: if (con == null)
416: throw new SQLException(CLOSED);
417: try {
418: return con.getTransactionIsolation();
419: } catch (SQLException e) {
420: setError(e);
421: throw e;
422: }
423: }
424:
425: public SQLWarning getWarnings() throws SQLException {
426: if (con == null)
427: throw new SQLException(CLOSED);
428: try {
429: return con.getWarnings();
430: } catch (SQLException e) {
431: setError(e);
432: throw e;
433: }
434: }
435:
436: public void clearWarnings() throws SQLException {
437: if (con == null)
438: throw new SQLException(CLOSED);
439: try {
440: con.clearWarnings();
441: } catch (SQLException e) {
442: setError(e);
443: throw e;
444: }
445: }
446:
447: public Statement createStatement(int resultSetType,
448: int resultSetConcurrency) throws SQLException {
449: if (con == null)
450: throw new SQLException(CLOSED);
451: try {
452: StatementInPool st = new StatementInPool(con
453: .createStatement(resultSetType,
454: resultSetConcurrency), this );
455: statements.add(st);
456: return st;
457: } catch (SQLException e) {
458: setError(e);
459: throw e;
460: }
461: }
462:
463: public PreparedStatement prepareStatement(String sql,
464: int resultSetType, int resultSetConcurrency)
465: throws SQLException {
466: if (con == null)
467: throw new SQLException(CLOSED);
468: try {
469: PreparedStatementInPool wrapper = null;
470: if (preparedStatementCacheSize >= 0) {
471: PreparedStatement ps = (PreparedStatement) preparedStatementCache
472: .useObject(sql);
473: if (ps == null)
474: throw new SQLException(
475: "Unable to create PreparedStatement!");
476: wrapper = new PreparedStatementInPool(ps, this , sql);
477: } else {
478: wrapper = new PreparedStatementInPool(con
479: .prepareStatement(sql, resultSetType,
480: resultSetConcurrency), this , sql);
481: }
482: statements.add(wrapper);
483: return wrapper;
484: } catch (SQLException e) {
485: setError(e);
486: throw e;
487: }
488: }
489:
490: /*
491: public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
492: return prepareStatement(sql);
493: }
494: */
495:
496: public CallableStatement prepareCall(String sql, int resultSetType,
497: int resultSetConcurrency) throws SQLException {
498: if (con == null)
499: throw new SQLException(CLOSED);
500: try {
501: return con.prepareCall(sql, resultSetType,
502: resultSetConcurrency);
503: } catch (SQLException e) {
504: setError(e);
505: throw e;
506: }
507: }
508:
509: public Map getTypeMap() throws SQLException {
510: if (con == null)
511: throw new SQLException(CLOSED);
512: try {
513: return con.getTypeMap();
514: } catch (SQLException e) {
515: setError(e);
516: throw e;
517: }
518: }
519:
520: public void setTypeMap(Map map) throws SQLException {
521: if (con == null)
522: throw new SQLException(CLOSED);
523: try {
524: con.setTypeMap(map);
525: } catch (SQLException e) {
526: setError(e);
527: throw e;
528: }
529: }
530:
531: // ------- J2SE 1.4 methods comment; needed to compile -------
532:
533: /* (non-Javadoc)
534: * @see java.sql.Connection#setHoldability(int)
535: */
536: public void setHoldability(int arg0) throws SQLException {
537: // TODO Auto-generated method stub
538:
539: }
540:
541: /* (non-Javadoc)
542: * @see java.sql.Connection#getHoldability()
543: */
544: public int getHoldability() throws SQLException {
545: // TODO Auto-generated method stub
546: return 0;
547: }
548:
549: /* (non-Javadoc)
550: * @see java.sql.Connection#setSavepoint()
551: */
552: public Savepoint setSavepoint() throws SQLException {
553: // TODO Auto-generated method stub
554: return null;
555: }
556:
557: /* (non-Javadoc)
558: * @see java.sql.Connection#setSavepoint(java.lang.String)
559: */
560: public Savepoint setSavepoint(String arg0) throws SQLException {
561: // TODO Auto-generated method stub
562: return null;
563: }
564:
565: /* (non-Javadoc)
566: * @see java.sql.Connection#rollback(java.sql.Savepoint)
567: */
568: public void rollback(Savepoint arg0) throws SQLException {
569: // TODO Auto-generated method stub
570:
571: }
572:
573: /* (non-Javadoc)
574: * @see java.sql.Connection#releaseSavepoint(java.sql.Savepoint)
575: */
576: public void releaseSavepoint(Savepoint arg0) throws SQLException {
577: // TODO Auto-generated method stub
578:
579: }
580:
581: /* (non-Javadoc)
582: * @see java.sql.Connection#createStatement(int, int, int)
583: */
584: public Statement createStatement(int arg0, int arg1, int arg2)
585: throws SQLException {
586: // TODO Auto-generated method stub
587: return null;
588: }
589:
590: /* (non-Javadoc)
591: * @see java.sql.Connection#prepareStatement(java.lang.String, int, int, int)
592: */
593: public PreparedStatement prepareStatement(String arg0, int arg1,
594: int arg2, int arg3) throws SQLException {
595: // TODO Auto-generated method stub
596: return null;
597: }
598:
599: /* (non-Javadoc)
600: * @see java.sql.Connection#prepareCall(java.lang.String, int, int, int)
601: */
602: public CallableStatement prepareCall(String arg0, int arg1,
603: int arg2, int arg3) throws SQLException {
604: // TODO Auto-generated method stub
605: return null;
606: }
607:
608: /* (non-Javadoc)
609: * @see java.sql.Connection#prepareStatement(java.lang.String, int)
610: */
611: public PreparedStatement prepareStatement(String arg0, int arg1)
612: throws SQLException {
613: // TODO Auto-generated method stub
614: return null;
615: }
616:
617: /* (non-Javadoc)
618: * @see java.sql.Connection#prepareStatement(java.lang.String, int[])
619: */
620: public PreparedStatement prepareStatement(String arg0, int[] arg1)
621: throws SQLException {
622: // TODO Auto-generated method stub
623: return null;
624: }
625:
626: /* (non-Javadoc)
627: * @see java.sql.Connection#prepareStatement(java.lang.String, java.lang.String[])
628: */
629: public PreparedStatement prepareStatement(String arg0, String[] arg1)
630: throws SQLException {
631: // TODO Auto-generated method stub
632: return null;
633: }
634: }
|