001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002// The Molly framework is freely distributable under the terms of an
003// MIT-style license. For details, see the molly pages web site at:
004// http://www.mollypages.org/. Use, modify, have fun !
005
006package fc.jdbc;
007
008import java.sql.*;
009import java.util.*;
010
011import fc.io.*;
012import fc.util.*;
013
014/** 
015A poolable {@link java.sql.Connection} that works with the {@link
016PooledConnectionMgr}. Closing this connection marks it as closed and
017returns it to the pool. 
018<p>
019The connection can be retrieved and closed as many times as needed.
020It is very important that a connection be closed after use (this
021is true for all connections, pooled or regular).
022<p>
023Typically one does so by saying:
024<blockquote>
025<pre>
026Connection con;
027try {
028  con = [..get either a pooled or regular connection..]
029  ..use..
030  <b>con.commit();</b>  //if transactional
031  }
032catch (SQLException e) {
033  <b>con.rollback();</b> //if transactional
034  }
035finally {
036  <b>con.close();</b>  //always (transactional or not)
037  }
038</pre>
039</blockquote>
040<b>Super Important</b>: transactions are not rolled back or committed when <code>close</code> is
041invoked. You must either commit or rollback the transaction if in transaction mode (ie you
042have called <code>setAutoCommit(false)</code> on this connection). Returning the connection (via close)
043back to the pool without committing/rolling-back will keep the prior statements around and those will
044be committed/rolledback at a later date if commit/rollback is invoked upon retrieving/using the connection
045again.
046
047@author hursh jain
048**/
049public final class PooledConnection implements Connection
050{
051static int idCounter = 1;
052
053//each pooled connection has an ID which is useful to track
054//check in/check outs from the pool. id's are assigned from
055//the idCount variable
056int           id;   
057PooledConnectionMgr   pool;   //the parent pool
058Connection        connection;
059volatile boolean    closed;
060//statistical data
061int           commitCount;
062int           rollbackCount;
063int           warningsCount;
064long          transaction_start_time = 0;
065
066/** 
067Creates a initially open pooled connection that wraps the
068specified jdbc connection.
069**/
070public PooledConnection(PooledConnectionMgr pool, Connection con) 
071  {
072  Argcheck.notnull(pool, "specified 'pool' arg was null");
073  Argcheck.notnull(con, "specifed 'con' arg was null");
074  this.pool = pool;
075  this.connection = con;
076  this.closed = false;
077  id = makeID(); 
078  }
079
080private static final synchronized int makeID() {
081  return idCounter++;
082  }
083
084protected int getID() {
085  return id;
086  }
087
088/** 
089<tt>true</tt> to mark this connection as closed, <tt>false</tt>
090to mark it as open.
091**/
092void setClosed(boolean val) 
093  {
094  closed = val;
095  }
096  
097void checkOpen() throws SQLException 
098  {
099  if (closed) {
100    throw new SQLException("This connection has been closed");
101    }
102    }
103  
104//== pooled  =========================================
105
106/**
107transactions are not rolled back or committed when <code>close</code> is invoked. You must either commit or 
108rollback the transaction if in transaction mode (ie you have called <code>setAutoCommit(false)</code> on this 
109connection). 
110<p>
111eturning the connection (via close) back to the pool without committing/rolling-back will keep 
112the prior statements around and those will be committed/rolledback at a later date if commit/rollback is 
113invoked upon retrieving/using the connection again.
114*/
115public void close() throws SQLException 
116  {
117  if (! isClosed() ) {
118    //pool.log.bug("closing connection", IOUtil.throwableToString(new Exception("Debug Stack Trace")));
119    setClosed(true);
120    pool.put(this);
121    }
122  else {
123    pool.log.bug("Tried to close already closed connection (#", id, ") =>", IOUtil.throwableToString(new Exception("Debug Stack Trace")));
124    }
125  }
126
127void closeReally() throws SQLException 
128  {
129  connection.close();
130  }
131
132
133public boolean isClosed() {
134    return closed;
135  }
136
137//we must use interned strings with id based hashmaps;
138private final Map pstmtMap = new IdentityHashMap(256);
139public PreparedStatement getCachedPreparedStatement(String sql) 
140throws SQLException
141  {
142  sql = sql.intern();
143  synchronized (this)
144    {
145    //intern can be tricky. we must use the string
146    //*returned* by the intern() method
147    if (pstmtMap.containsKey(sql)) {
148      final PreparedStatement ps = (PreparedStatement) pstmtMap.get(sql);
149      ps.clearParameters();
150      return ps;
151      }
152    else{
153      final PreparedStatement pstmt = this.prepareStatement(  
154            sql,
155            ResultSet.TYPE_SCROLL_INSENSITIVE,
156            ResultSet.CONCUR_READ_ONLY);
157      pstmtMap.put(sql, pstmt);
158      return pstmt;
159      }
160    }
161  }
162
163public String toString() {
164  StringBuilder buf = new StringBuilder();
165  buf.append(super.toString());
166  buf.append("/PoolID:");
167  buf.append(id);
168  try {
169    buf.append("/currentIsolation:");
170    int i = getTransactionIsolation();
171    switch (i)
172      {
173      case Connection.TRANSACTION_NONE: 
174        buf.append("NONE"); break;
175      case Connection.TRANSACTION_READ_UNCOMMITTED : 
176        buf.append("READ_UNCOMMITTED"); break;
177      case Connection.TRANSACTION_READ_COMMITTED: 
178        buf.append("READ_COMMITTED"); break;
179      case Connection.TRANSACTION_REPEATABLE_READ : 
180        buf.append("REPEATABLE_READ"); break;
181      case Connection.TRANSACTION_SERIALIZABLE : 
182        buf.append("SERIALIZABLE"); break;
183      default:        
184        buf.append("Unknown: ");
185        buf.append(i);
186      }
187    buf.append("/autoCommit:");
188    buf.append(String.valueOf(getAutoCommit()).toUpperCase());
189    if (transaction_start_time == 0) {
190      buf.append("/[no open transaction]");
191      }
192    else{
193      buf.append("/[in open transaction,");
194      buf.append(System.currentTimeMillis() - transaction_start_time);
195      buf.append(" ms]");
196      }
197    }
198  catch (SQLException e) {
199    }
200  return buf.toString();
201  }
202
203
204
205
206//== wrapped methods ====================================
207
208public void clearWarnings() throws SQLException {
209    connection.clearWarnings();
210  }
211
212public void commit() throws SQLException 
213  {
214    commitCount++;
215  transaction_start_time = 0;
216    connection.commit();
217  }
218
219public Statement createStatement() throws SQLException {
220    return connection.createStatement();
221  }
222
223public Statement createStatement(int resultSetType, 
224                                 int resultSetConcurrency) throws SQLException {
225    
226    return connection.createStatement(resultSetType, resultSetConcurrency);
227  }
228
229public boolean getAutoCommit() throws SQLException {
230    return connection.getAutoCommit();
231  }
232
233public String getCatalog() throws SQLException {
234    return connection.getCatalog();
235  }
236
237public DatabaseMetaData getMetaData() throws SQLException {
238    return connection.getMetaData();
239  }
240
241public int getTransactionIsolation() throws SQLException {
242    return connection.getTransactionIsolation();
243  }
244
245public Map getTypeMap() throws SQLException {
246    return connection.getTypeMap();
247  }
248
249public SQLWarning getWarnings() throws SQLException {
250    warningsCount++;
251    return connection.getWarnings();
252  }
253
254
255public boolean isReadOnly() throws SQLException {
256    return connection.isReadOnly();
257  }
258
259public String nativeSQL(String sql) throws SQLException {
260    return connection.nativeSQL(sql);
261  }    
262
263public CallableStatement prepareCall(String sql) throws SQLException {
264    return connection.prepareCall(sql);
265  }
266
267public CallableStatement prepareCall(String sql, int resultSetType, 
268                                     int resultSetConcurrency) throws SQLException {
269    return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
270  }
271
272public PreparedStatement prepareStatement(String sql) throws SQLException {
273    return connection.prepareStatement(sql);
274  }
275
276public PreparedStatement prepareStatement(String sql, int resultSetType, 
277                                          int resultSetConcurrency) throws SQLException {
278    return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
279  }
280
281public void rollback() throws SQLException {
282  connection.rollback();
283  }
284
285public void setAutoCommit(boolean b) throws SQLException  
286  {
287  if (b)
288      transaction_start_time = 0;
289  else
290    transaction_start_time = System.currentTimeMillis();
291
292    connection.setAutoCommit(b);
293  }
294    
295public void setCatalog(String catalog) throws SQLException {
296    connection.setCatalog(catalog);
297  }
298
299public void setReadOnly(boolean readOnly) throws SQLException {
300    connection.setReadOnly(readOnly);
301  }
302
303public void setTransactionIsolation(int level) throws SQLException {
304    connection.setTransactionIsolation(level);
305  }
306
307public void setTypeMap(Map map) throws SQLException {
308    connection.setTypeMap(map);
309  }
310
311public int getHoldability() throws SQLException {
312    return connection.getHoldability();
313  }
314
315public void setHoldability(int holdability) throws SQLException {
316    connection.setHoldability(holdability);
317  }
318
319public java.sql.Savepoint setSavepoint() throws SQLException {
320    return connection.setSavepoint();
321  }
322
323public java.sql.Savepoint setSavepoint(String name) throws SQLException {
324    return connection.setSavepoint(name);
325  }
326
327public void rollback(java.sql.Savepoint savepoint) throws SQLException {
328    rollbackCount++;
329    connection.rollback(savepoint);
330  }
331
332public void releaseSavepoint(java.sql.Savepoint savepoint) throws SQLException {
333    connection.releaseSavepoint(savepoint);
334  }
335
336public Statement createStatement(int resultSetType,
337                                 int resultSetConcurrency,
338                                 int resultSetHoldability) throws SQLException  {
339    return connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
340  }
341
342public Struct createStruct(String str, Object[] arr) throws SQLException  {
343    return connection.createStruct(str, arr);
344  }
345
346public java.util.Properties getClientInfo() throws SQLException {
347  return connection.getClientInfo();
348  }
349
350public String getClientInfo(String name) throws SQLException {
351  return connection.getClientInfo(name);
352  }
353
354public void setClientInfo(String key, String val) throws SQLClientInfoException {
355  connection.setClientInfo(key, val);
356  }
357
358public void setClientInfo(java.util.Properties props) throws SQLClientInfoException {
359  connection.setClientInfo(props);
360  }
361                         
362public java.sql.Array createArrayOf(String str, Object[] arr) throws SQLException  {
363    return connection.createArrayOf(str, arr);
364  }
365
366public Blob createBlob() throws SQLException {
367  return connection.createBlob();
368  }
369
370public Clob createClob() throws SQLException {
371  return connection.createClob();
372  }
373
374public NClob createNClob() throws SQLException {
375  return connection.createNClob();
376  }
377
378public SQLXML createSQLXML() throws SQLException  {
379  return connection.createSQLXML();
380  }
381                
382public boolean isValid(int timeout) throws SQLException {
383  return connection.isValid(timeout);
384  }
385
386public boolean isWrapperFor(Class iface) throws SQLException {
387  return connection.isWrapperFor(iface);
388  }
389
390public Object unwrap(Class iface) throws SQLException {
391  return connection.unwrap(iface);
392  }
393
394public PreparedStatement prepareStatement(String sql, int resultSetType,
395                                          int resultSetConcurrency,
396                                          int resultSetHoldability) throws SQLException  {
397    return connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
398  }
399
400public CallableStatement prepareCall(String sql, int resultSetType,
401                                     int resultSetConcurrency,
402                                     int resultSetHoldability) throws SQLException {
403    return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
404  }
405
406public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
407    return connection.prepareStatement(sql, autoGeneratedKeys);
408  }
409
410  public PreparedStatement prepareStatement(String sql, int columnIndexes[]) throws SQLException {
411    return connection.prepareStatement(sql, columnIndexes);
412  }
413
414public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException {
415  return connection.prepareStatement(sql, columnNames);
416  }
417
418//====================== 1.7 + ===============================
419public int getNetworkTimeout() 
420throws SQLException {
421  return connection.getNetworkTimeout();
422  }
423  
424public void setNetworkTimeout(java.util.concurrent.Executor executor, int milliseconds) 
425throws SQLException {
426  connection.setNetworkTimeout(executor, milliseconds);
427  } 
428
429public void abort(java.util.concurrent.Executor executor)
430throws SQLException {
431  connection.abort(executor);
432  }
433
434public String getSchema()
435throws SQLException {
436  return connection.getSchema();
437  }
438          
439public void setSchema(String schema)
440throws SQLException {
441  connection.setSchema(schema);
442  }
443
444} //~PooledConnection