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    
006    package fc.jdbc;
007    
008    import java.sql.*;
009    import java.util.*;
010    
011    import fc.io.*;
012    import fc.util.*;
013    
014    /** 
015    A poolable {@link java.sql.Connection} that works with the {@link
016    PooledConnectionMgr}. Closing this connection marks it as closed and
017    returns it to the pool. 
018    <p>
019    The connection can be retrieved and closed as many times as needed.
020    It is very important that a connection be closed after use (this
021    is true for all connections, pooled or regular).
022    <p>
023    Typically one does so by saying:
024    <blockquote>
025    <pre>
026    Connection con;
027    try {
028      con = [..get either a pooled or regular connection..]
029      ..use..
030      <b>con.commit();</b>  //if transactional
031      }
032    catch (SQLException e) {
033      <b>con.rollback();</b> //if transactional
034      }
035    finally {
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
041    invoked. You must either commit or rollback the transaction if in transaction mode (ie you
042    have called <code>setAutoCommit(false)</code> on this connection). Returning the connection (via close)
043    back to the pool without committing/rolling-back will keep the prior statements around and those will
044    be committed/rolledback at a later date if commit/rollback is invoked upon retrieving/using the connection
045    again.
046    
047    @author hursh jain
048    **/
049    public class PooledConnection implements Connection
050    {
051    static 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
056    int           id;   
057    PooledConnectionMgr   pool;   //the parent pool
058    Connection        connection;
059    volatile boolean    closed;
060    //statistical data
061    int           commitCount;
062    int           rollbackCount;
063    int           warningsCount;
064    long          transaction_start_time = 0;
065    
066    /** 
067    Creates a initially open pooled connection that wraps the
068    specified jdbc connection.
069    **/
070    public 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    
080    private static final synchronized int makeID() {
081      return idCounter++;
082      }
083    
084    protected int getID() {
085      return id;
086      }
087    
088    /** 
089    <tt>true</tt> to mark this connection as closed, <tt>false</tt>
090    to mark it as open.
091    **/
092    void setClosed(boolean val) 
093      {
094      closed = val;
095      }
096      
097    void checkOpen() throws SQLException 
098      {
099      if (closed) {
100        throw new SQLException("This connection has been closed");
101        }
102        }
103      
104    //== pooled  =========================================
105    
106    /**
107    transactions are not rolled back or committed when <code>close</code> is invoked. You must either commit or 
108    rollback the transaction if in transaction mode (ie you have called <code>setAutoCommit(false)</code> on this 
109    connection). 
110    <p>
111    eturning the connection (via close) back to the pool without committing/rolling-back will keep 
112    the prior statements around and those will be committed/rolledback at a later date if commit/rollback is 
113    invoked upon retrieving/using the connection again.
114    */
115    public 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    
127    void closeReally() throws SQLException 
128      {
129      connection.close();
130      }
131    
132    
133    public boolean isClosed() {
134        return closed;
135      }
136    
137    //we must use interned strings with id based hashmaps;
138    private final Map pstmtMap = new IdentityHashMap(256);
139    public PreparedStatement getCachedPreparedStatement(String sql) 
140    throws 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    
163    public 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    
208    public void clearWarnings() throws SQLException {
209        connection.clearWarnings();
210      }
211    
212    public void commit() throws SQLException 
213      {
214        commitCount++;
215      transaction_start_time = 0;
216        connection.commit();
217      }
218    
219    public Statement createStatement() throws SQLException {
220        return connection.createStatement();
221      }
222    
223    public Statement createStatement(int resultSetType, 
224                                     int resultSetConcurrency) throws SQLException {
225        
226        return connection.createStatement(resultSetType, resultSetConcurrency);
227      }
228    
229    public boolean getAutoCommit() throws SQLException {
230        return connection.getAutoCommit();
231      }
232    
233    public String getCatalog() throws SQLException {
234        return connection.getCatalog();
235      }
236    
237    public DatabaseMetaData getMetaData() throws SQLException {
238        return connection.getMetaData();
239      }
240    
241    public int getTransactionIsolation() throws SQLException {
242        return connection.getTransactionIsolation();
243      }
244    
245    public Map getTypeMap() throws SQLException {
246        return connection.getTypeMap();
247      }
248    
249    public SQLWarning getWarnings() throws SQLException {
250        warningsCount++;
251        return connection.getWarnings();
252      }
253    
254    
255    public boolean isReadOnly() throws SQLException {
256        return connection.isReadOnly();
257      }
258    
259    public String nativeSQL(String sql) throws SQLException {
260        return connection.nativeSQL(sql);
261      }    
262    
263    public CallableStatement prepareCall(String sql) throws SQLException {
264        return connection.prepareCall(sql);
265      }
266    
267    public CallableStatement prepareCall(String sql, int resultSetType, 
268                                         int resultSetConcurrency) throws SQLException {
269        return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
270      }
271    
272    public PreparedStatement prepareStatement(String sql) throws SQLException {
273        return connection.prepareStatement(sql);
274      }
275    
276    public PreparedStatement prepareStatement(String sql, int resultSetType, 
277                                              int resultSetConcurrency) throws SQLException {
278        return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
279      }
280    
281    public void rollback() throws SQLException {
282      connection.rollback();
283      }
284    
285    public 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        
295    public void setCatalog(String catalog) throws SQLException {
296        connection.setCatalog(catalog);
297      }
298    
299    public void setReadOnly(boolean readOnly) throws SQLException {
300        connection.setReadOnly(readOnly);
301      }
302    
303    public void setTransactionIsolation(int level) throws SQLException {
304        connection.setTransactionIsolation(level);
305      }
306    
307    public void setTypeMap(Map map) throws SQLException {
308        connection.setTypeMap(map);
309      }
310    
311    public int getHoldability() throws SQLException {
312        return connection.getHoldability();
313      }
314    
315    public void setHoldability(int holdability) throws SQLException {
316        connection.setHoldability(holdability);
317      }
318    
319    public java.sql.Savepoint setSavepoint() throws SQLException {
320        return connection.setSavepoint();
321      }
322    
323    public java.sql.Savepoint setSavepoint(String name) throws SQLException {
324        return connection.setSavepoint(name);
325      }
326    
327    public void rollback(java.sql.Savepoint savepoint) throws SQLException {
328        rollbackCount++;
329        connection.rollback(savepoint);
330      }
331    
332    public void releaseSavepoint(java.sql.Savepoint savepoint) throws SQLException {
333        connection.releaseSavepoint(savepoint);
334      }
335    
336    public Statement createStatement(int resultSetType,
337                                     int resultSetConcurrency,
338                                     int resultSetHoldability) throws SQLException  {
339        return connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
340      }
341    
342    public Struct createStruct(String str, Object[] arr) throws SQLException  {
343        return connection.createStruct(str, arr);
344      }
345    
346    public java.util.Properties getClientInfo() throws SQLException {
347      return connection.getClientInfo();
348      }
349    
350    public String getClientInfo(String name) throws SQLException {
351      return connection.getClientInfo(name);
352      }
353    
354    public void setClientInfo(String key, String val) throws SQLClientInfoException {
355      connection.setClientInfo(key, val);
356      }
357    
358    public void setClientInfo(java.util.Properties props) throws SQLClientInfoException {
359      connection.setClientInfo(props);
360      }
361                             
362    public java.sql.Array createArrayOf(String str, Object[] arr) throws SQLException  {
363        return connection.createArrayOf(str, arr);
364      }
365    
366    public Blob createBlob() throws SQLException {
367      return connection.createBlob();
368      }
369    
370    public Clob createClob() throws SQLException {
371      return connection.createClob();
372      }
373    
374    public NClob createNClob() throws SQLException {
375      return connection.createNClob();
376      }
377    
378    public SQLXML createSQLXML() throws SQLException  {
379      return connection.createSQLXML();
380      }
381                    
382    public boolean isValid(int timeout) throws SQLException {
383      return connection.isValid(timeout);
384      }
385    
386    public boolean isWrapperFor(Class iface) throws SQLException {
387      return connection.isWrapperFor(iface);
388      }
389    
390    public Object unwrap(Class iface) throws SQLException {
391      return connection.unwrap(iface);
392      }
393    
394    public PreparedStatement prepareStatement(String sql, int resultSetType,
395                                              int resultSetConcurrency,
396                                              int resultSetHoldability) throws SQLException  {
397        return connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
398      }
399    
400    public CallableStatement prepareCall(String sql, int resultSetType,
401                                         int resultSetConcurrency,
402                                         int resultSetHoldability) throws SQLException {
403        return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
404      }
405    
406    public 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    
414    public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException {
415      return connection.prepareStatement(sql, columnNames);
416      }
417    
418    //====================== 1.7 + ===============================
419    /* enable this when building for jdk7+ */
420    /*
421    public int getNetworkTimeout() 
422    throws SQLException {
423      return connection.getNetworkTimeout();
424      }
425      
426    public void setNetworkTimeout(java.util.concurrent.Executor executor, int milliseconds) 
427    throws SQLException {
428      connection.setNetworkTimeout(executor, milliseconds);
429      } 
430    
431    public void abort(java.util.concurrent.Executor executor)
432    throws SQLException {
433      connection.abort(executor);
434      }
435    
436    public String getSchema()
437    throws SQLException {
438      return connection.getSchema();
439      }
440              
441    public void setSchema(String schema)
442    throws SQLException {
443      connection.setSchema(schema);
444      }
445    */
446    } //~PooledConnection