View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.dbcp.cpdsadapter;
19  
20  import java.sql.Connection;
21  import java.sql.PreparedStatement;
22  import java.sql.SQLException;
23  import java.util.Arrays;
24  import java.util.Vector;
25  
26  import javax.sql.ConnectionEvent;
27  import javax.sql.ConnectionEventListener;
28  import javax.sql.PooledConnection;
29  /* JDBC_4_ANT_KEY_BEGIN */
30  import javax.sql.StatementEventListener;
31  /* JDBC_4_ANT_KEY_END */
32  
33  import org.apache.commons.dbcp.DelegatingConnection;
34  import org.apache.commons.dbcp.DelegatingPreparedStatement;
35  import org.apache.commons.dbcp.SQLNestedException;
36  import org.apache.commons.pool.KeyedObjectPool;
37  import org.apache.commons.pool.KeyedPoolableObjectFactory;
38  
39  /**
40   * Implementation of PooledConnection that is returned by
41   * PooledConnectionDataSource.
42   *
43   * @author John D. McNally
44   * @version $Revision: 1023401 $ $Date: 2010-10-16 21:54:24 -0400 (Sat, 16 Oct 2010) $
45   */
46  class PooledConnectionImpl 
47          implements PooledConnection, KeyedPoolableObjectFactory {
48      private static final String CLOSED 
49              = "Attempted to use PooledConnection after closed() was called.";
50  
51      /**
52       * The JDBC database connection that represents the physical db connection.
53       */
54      private Connection connection = null;
55      
56      /**
57       * A DelegatingConnection used to create a PoolablePreparedStatementStub
58       */
59      private final DelegatingConnection delegatingConnection;
60  
61      /**
62       * The JDBC database logical connection.
63       */
64      private Connection logicalConnection = null;
65  
66      /**
67       * ConnectionEventListeners
68       */
69      private final Vector eventListeners;
70  
71      /**
72       * StatementEventListeners
73       */
74      private final Vector statementEventListeners = new Vector();
75  
76      /**
77       * flag set to true, once close() is called.
78       */
79      boolean isClosed; // TODO - make private?
80  
81      /** My pool of {*link PreparedStatement}s. */
82      // TODO - make final?
83      protected KeyedObjectPool pstmtPool = null;
84  
85      /** 
86       * Controls access to the underlying connection 
87       */
88      private boolean accessToUnderlyingConnectionAllowed = false; 
89  
90      /**
91       * Wrap the real connection.
92       * @param connection the connection to be wrapped
93       * @param pool the pool to use
94       */
95      PooledConnectionImpl(Connection connection, KeyedObjectPool pool) {
96          this.connection = connection;
97          if (connection instanceof DelegatingConnection) {
98              this.delegatingConnection = (DelegatingConnection) connection;
99          } else {
100             this.delegatingConnection = new DelegatingConnection(connection);
101         }
102         eventListeners = new Vector();
103         isClosed = false;
104         if (pool != null) {
105             pstmtPool = pool;
106             pstmtPool.setFactory(this);            
107         }
108     }
109 
110     /**
111      * {@inheritDoc}
112      */
113     public void addConnectionEventListener(ConnectionEventListener listener) {
114         if (!eventListeners.contains(listener)) {
115             eventListeners.add(listener);
116         }
117     }
118 
119     /* JDBC_4_ANT_KEY_BEGIN */
120     public void addStatementEventListener(StatementEventListener listener) {
121         if (!statementEventListeners.contains(listener)) {
122             statementEventListeners.add(listener);
123         }
124     }
125     /* JDBC_4_ANT_KEY_END */
126 
127     /**
128      * Closes the physical connection and marks this 
129      * <code>PooledConnection</code> so that it may not be used 
130      * to generate any more logical <code>Connection</code>s.
131      *
132      * @exception SQLException if an error occurs or the connection is already closed
133      */
134     public void close() throws SQLException {        
135         assertOpen();
136         isClosed = true;
137         try {
138             if (pstmtPool != null) {
139                 try {
140                     pstmtPool.close();
141                 } finally {
142                     pstmtPool = null;
143                 }
144             }
145         } catch (RuntimeException e) {
146             throw e;
147         } catch (Exception e) {
148             throw new SQLNestedException("Cannot close connection (return to pool failed)", e);
149         } finally {
150             try {
151                 connection.close();
152             } finally {
153                 connection = null;
154             }
155         }
156     }
157 
158     /**
159      * Throws an SQLException, if isClosed is true
160      */
161     private void assertOpen() throws SQLException {
162         if (isClosed) {
163             throw new SQLException(CLOSED);
164         }
165     }
166 
167     /**
168      * Returns a JDBC connection.
169      *
170      * @return The database connection.
171      * @throws SQLException if the connection is not open or the previous logical connection is still open
172      */
173     public Connection getConnection() throws SQLException {
174         assertOpen();
175         // make sure the last connection is marked as closed
176         if (logicalConnection != null && !logicalConnection.isClosed()) {
177             // should notify pool of error so the pooled connection can
178             // be removed !FIXME!
179             throw new SQLException("PooledConnection was reused, without" 
180                     + "its previous Connection being closed.");
181         }
182 
183         // the spec requires that this return a new Connection instance.
184         logicalConnection = new ConnectionImpl(
185                 this, connection, isAccessToUnderlyingConnectionAllowed());
186         return logicalConnection;
187     }
188 
189     /**
190      * {@inheritDoc}
191      */
192     public void removeConnectionEventListener(
193             ConnectionEventListener listener) {
194         eventListeners.remove(listener);
195     }
196 
197     /* JDBC_4_ANT_KEY_BEGIN */
198     public void removeStatementEventListener(StatementEventListener listener) {
199         statementEventListeners.remove(listener);
200     }
201     /* JDBC_4_ANT_KEY_END */
202 
203     /**
204      * Closes the physical connection and checks that the logical connection
205      * was closed as well.
206      */
207     protected void finalize() throws Throwable {
208         // Closing the Connection ensures that if anyone tries to use it,
209         // an error will occur.
210         try {
211             connection.close();
212         } catch (Exception ignored) {
213         }
214 
215         // make sure the last connection is marked as closed
216         if (logicalConnection != null && !logicalConnection.isClosed()) {
217             throw new SQLException("PooledConnection was gc'ed, without" 
218                     + "its last Connection being closed.");
219         }        
220     }
221 
222     /**
223      * sends a connectionClosed event.
224      */
225     void notifyListeners() {
226         ConnectionEvent event = new ConnectionEvent(this);
227         Object[] listeners = eventListeners.toArray();
228         for (int i = 0; i < listeners.length; i++) {
229             ((ConnectionEventListener) listeners[i]).connectionClosed(event);
230         }
231     }
232 
233     // -------------------------------------------------------------------
234     // The following code implements a PreparedStatement pool
235 
236     /**
237      * Create or obtain a {@link PreparedStatement} from my pool.
238      * @param sql the SQL statement
239      * @return a {@link PoolablePreparedStatement}
240      */
241     PreparedStatement prepareStatement(String sql) throws SQLException {
242         if (pstmtPool == null) {
243             return connection.prepareStatement(sql);
244         } else {
245             try {
246                 return (PreparedStatement) 
247                         pstmtPool.borrowObject(createKey(sql));
248             } catch (RuntimeException e) {
249                 throw e;
250             } catch (Exception e) {
251                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
252             }
253         }
254     }
255 
256     /**
257      * Create or obtain a {@link PreparedStatement} from my pool.
258      * @param sql a <code>String</code> object that is the SQL statement to
259      *            be sent to the database; may contain one or more '?' IN
260      *            parameters
261      * @param resultSetType a result set type; one of 
262      *         <code>ResultSet.TYPE_FORWARD_ONLY</code>, 
263      *         <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
264      *         <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
265      * @param resultSetConcurrency a concurrency type; one of
266      *         <code>ResultSet.CONCUR_READ_ONLY</code> or
267      *         <code>ResultSet.CONCUR_UPDATABLE</code>
268      * 
269      * @return a {@link PoolablePreparedStatement}
270      * @see Connection#prepareStatement(String, int, int)
271      */
272     PreparedStatement prepareStatement(String sql, int resultSetType, 
273                                        int resultSetConcurrency) 
274             throws SQLException {
275         if (pstmtPool == null) {
276             return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
277         } else {
278             try {
279                 return (PreparedStatement) pstmtPool.borrowObject(
280                     createKey(sql,resultSetType,resultSetConcurrency));
281             } catch (RuntimeException e) {
282                 throw e;
283             } catch (Exception e) {
284                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
285             }
286         }
287     }
288 
289     /**
290      * Create or obtain a {@link PreparedStatement} from my pool.
291      * @param sql an SQL statement that may contain one or more '?' IN
292      *        parameter placeholders
293      * @param autoGeneratedKeys a flag indicating whether auto-generated keys 
294      *        should be returned; one of
295      *        <code>Statement.RETURN_GENERATED_KEYS</code> or
296      *        <code>Statement.NO_GENERATED_KEYS</code>  
297      * @return a {@link PoolablePreparedStatement}
298      * @see Connection#prepareStatement(String, int)
299      */
300     PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) 
301             throws SQLException {
302         if (pstmtPool == null) {
303             return connection.prepareStatement(sql, autoGeneratedKeys);
304         } else {
305             try {
306                 return (PreparedStatement) pstmtPool.borrowObject(
307                     createKey(sql,autoGeneratedKeys));
308             } catch (RuntimeException e) {
309                 throw e;
310             } catch (Exception e) {
311                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
312             }
313         }
314     }
315 
316     PreparedStatement prepareStatement(String sql, int resultSetType,
317             int resultSetConcurrency, int resultSetHoldability)
318     throws SQLException {
319         if (pstmtPool == null) {
320             return connection.prepareStatement(sql, resultSetType,
321                     resultSetConcurrency, resultSetHoldability);
322         } else {
323             try {
324                 return (PreparedStatement) pstmtPool.borrowObject(
325                     createKey(sql, resultSetType, resultSetConcurrency,
326                             resultSetHoldability));
327             } catch (RuntimeException e) {
328                 throw e;
329             } catch (Exception e) {
330                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
331             }
332         }
333     }
334 
335     PreparedStatement prepareStatement(String sql, int columnIndexes[])
336     throws SQLException {
337         if (pstmtPool == null) {
338             return connection.prepareStatement(sql, columnIndexes);
339         } else {
340             try {
341                 return (PreparedStatement) pstmtPool.borrowObject(
342                     createKey(sql, columnIndexes));
343             } catch (RuntimeException e) {
344                 throw e;
345             } catch (Exception e) {
346                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
347             }
348         }
349     }
350 
351     PreparedStatement prepareStatement(String sql, String columnNames[])
352     throws SQLException {
353         if (pstmtPool == null) {
354             return connection.prepareStatement(sql, columnNames);
355         } else {
356             try {
357                 return (PreparedStatement) pstmtPool.borrowObject(
358                     createKey(sql, columnNames));
359             } catch (RuntimeException e) {
360                 throw e;
361             } catch (Exception e) {
362                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
363             }
364         }
365     }
366 
367     /**
368      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
369      */
370     protected Object createKey(String sql, int autoGeneratedKeys) {
371         return new PStmtKey(normalizeSQL(sql), autoGeneratedKeys);
372     }
373 
374     /**
375      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
376      */
377     protected Object createKey(String sql, int resultSetType,
378             int resultSetConcurrency, int resultSetHoldability) {
379         return new PStmtKey(normalizeSQL(sql), resultSetType,
380                 resultSetConcurrency, resultSetHoldability);
381     }
382 
383     /**
384      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
385      */
386     protected Object createKey(String sql, int columnIndexes[]) {
387         return new PStmtKey(normalizeSQL(sql), columnIndexes);
388     }
389 
390     /**
391      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
392      */
393     protected Object createKey(String sql, String columnNames[]) {
394         return new PStmtKey(normalizeSQL(sql), columnNames);
395     }
396 
397     /**
398      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
399      */
400     protected Object createKey(String sql, int resultSetType, 
401                                int resultSetConcurrency) {
402         return new PStmtKey(normalizeSQL(sql), resultSetType,
403                             resultSetConcurrency);
404     }
405     
406     /**
407      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
408      */
409     protected Object createKey(String sql) {
410         return new PStmtKey(normalizeSQL(sql));
411     }
412 
413     /**
414      * Normalize the given SQL statement, producing a
415      * cannonical form that is semantically equivalent to the original.
416      */
417     protected String normalizeSQL(String sql) {
418         return sql.trim();
419     }
420 
421     /**
422      * My {*link KeyedPoolableObjectFactory} method for creating
423      * {*link PreparedStatement}s.
424      * @param obj the key for the {*link PreparedStatement} to be created
425      */
426     public Object makeObject(Object obj) throws Exception {
427         if (null == obj || !(obj instanceof PStmtKey)) {
428             throw new IllegalArgumentException();
429         } else {
430             // _openPstmts++;
431             PStmtKey key = (PStmtKey)obj;
432             if (null == key._resultSetType 
433                     && null == key._resultSetConcurrency) {
434                 if (null == key._autoGeneratedKeys) {
435                     return new PoolablePreparedStatementStub(
436                             connection.prepareStatement(key._sql),
437                             key, pstmtPool, delegatingConnection);
438                 } else {
439                     return new PoolablePreparedStatementStub(
440                             connection.prepareStatement(key._sql,
441                                     key._autoGeneratedKeys.intValue()),
442                             key, pstmtPool, delegatingConnection);
443                 }
444             } else {
445                 return new PoolablePreparedStatementStub(
446                         connection.prepareStatement(key._sql,
447                         key._resultSetType.intValue(),
448                         key._resultSetConcurrency.intValue()),
449                         key, pstmtPool, delegatingConnection);
450             }
451         }
452     }
453 
454     /**
455      * My {*link KeyedPoolableObjectFactory} method for destroying
456      * {*link PreparedStatement}s.
457      * @param key ignored
458      * @param obj the {*link PreparedStatement} to be destroyed.
459      */
460     public void destroyObject(Object key, Object obj) throws Exception {
461         //_openPstmts--;
462         if (obj instanceof DelegatingPreparedStatement) {
463             ((DelegatingPreparedStatement) obj).getInnermostDelegate().close();
464         } else {
465             ((PreparedStatement) obj).close();
466         }
467     }
468 
469     /**
470      * My {*link KeyedPoolableObjectFactory} method for validating
471      * {*link PreparedStatement}s.
472      * @param key ignored
473      * @param obj ignored
474      * @return <tt>true</tt>
475      */
476     public boolean validateObject(Object key, Object obj) {
477         return true;
478     }
479 
480     /**
481      * My {*link KeyedPoolableObjectFactory} method for activating
482      * {*link PreparedStatement}s.
483      * @param key ignored
484      * @param obj ignored
485      */
486     public void activateObject(Object key, Object obj) throws Exception {
487         ((PoolablePreparedStatementStub) obj).activate();
488     }
489 
490     /**
491      * My {*link KeyedPoolableObjectFactory} method for passivating
492      * {*link PreparedStatement}s.  Currently invokes {*link PreparedStatement#clearParameters}.
493      * @param key ignored
494      * @param obj a {*link PreparedStatement}
495      */
496     public void passivateObject(Object key, Object obj) throws Exception {
497         ((PreparedStatement) obj).clearParameters();
498         ((PoolablePreparedStatementStub) obj).passivate();
499     }
500 
501     /**
502      * Returns the value of the accessToUnderlyingConnectionAllowed property.
503      * 
504      * @return true if access to the underlying is allowed, false otherwise.
505      */
506     public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
507         return this.accessToUnderlyingConnectionAllowed;
508     }
509 
510     /**
511      * Sets the value of the accessToUnderlyingConnectionAllowed property.
512      * It controls if the PoolGuard allows access to the underlying connection.
513      * (Default: false)
514      * 
515      * @param allow Access to the underlying connection is granted when true.
516      */
517     public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
518         this.accessToUnderlyingConnectionAllowed = allow;
519     }
520     
521     /**
522      * A key uniquely identifying {*link PreparedStatement}s.
523      */
524     static class PStmtKey {
525         protected String _sql = null;
526         protected Integer _resultSetType = null;
527         protected Integer _resultSetConcurrency = null;
528         protected Integer _autoGeneratedKeys = null;
529         protected Integer _resultSetHoldability = null;
530         protected int _columnIndexes[] = null;
531         protected String _columnNames[] = null;
532         
533         PStmtKey(String sql) {
534             _sql = sql;
535         }
536 
537         PStmtKey(String sql, int resultSetType, int resultSetConcurrency) {
538             _sql = sql;
539             _resultSetType = new Integer(resultSetType);
540             _resultSetConcurrency = new Integer(resultSetConcurrency);
541         }
542 
543         PStmtKey(String sql, int autoGeneratedKeys) {
544             _sql = sql;
545             _autoGeneratedKeys = new Integer(autoGeneratedKeys);
546         }
547 
548         PStmtKey(String sql, int resultSetType, int resultSetConcurrency,
549                 int resultSetHoldability) {
550             _sql = sql;
551             _resultSetType = new Integer(resultSetType);
552             _resultSetConcurrency = new Integer(resultSetConcurrency);
553             _resultSetHoldability = new Integer(resultSetHoldability);
554         }
555 
556         PStmtKey(String sql, int columnIndexes[]) {
557             _sql = sql;
558             _columnIndexes = columnIndexes;
559         }
560 
561         PStmtKey(String sql, String columnNames[]) {
562             _sql = sql;
563             _columnNames = columnNames;
564         }
565 
566         
567         public boolean equals(Object that) {
568             try {
569                 PStmtKey key = (PStmtKey) that;
570                 return(((null == _sql && null == key._sql) || _sql.equals(key._sql)) &&
571                        ((null == _resultSetType && null == key._resultSetType) || _resultSetType.equals(key._resultSetType)) &&
572                        ((null == _resultSetConcurrency && null == key._resultSetConcurrency) || _resultSetConcurrency.equals(key._resultSetConcurrency)) &&
573                        ((null == _autoGeneratedKeys && null == key._autoGeneratedKeys) || _autoGeneratedKeys.equals(key._autoGeneratedKeys)) &&
574                        ((null == _resultSetHoldability && null == key._resultSetHoldability) || _resultSetHoldability.equals(key._resultSetHoldability)) &&
575                        ((null == _columnIndexes && null == key._columnIndexes) || Arrays.equals(_columnIndexes, key._columnIndexes)) &&
576                        ((null == _columnNames && null == key._columnNames) || Arrays.equals(_columnNames, key._columnNames))
577                       );
578             } catch (ClassCastException e) {
579                 return false;
580             } catch (NullPointerException e) {
581                 return false;
582             }
583         }
584 
585         public int hashCode() {
586             return(null == _sql ? 0 : _sql.hashCode());
587         }
588 
589         public String toString() {
590             StringBuffer buf = new StringBuffer();
591             buf.append("PStmtKey: sql=");
592             buf.append(_sql);
593             buf.append(", resultSetType=");
594             buf.append(_resultSetType);
595             buf.append(", resultSetConcurrency=");
596             buf.append(_resultSetConcurrency);
597             buf.append(", autoGeneratedKeys=");
598             buf.append(_autoGeneratedKeys);
599             buf.append(", resultSetHoldability=");
600             buf.append(_resultSetHoldability);
601             buf.append(", columnIndexes=");
602 // JDK1.5   buf.append(Arrays.toString(_columnIndexes));
603             arrayToString(buf,_columnIndexes);
604             buf.append(", columnNames=");
605 // JDK1.5   buf.append(Arrays.toString(_columnNames));
606             arrayToString(buf,_columnNames);
607             return buf.toString();
608         }
609         private void arrayToString(StringBuffer sb, int[] array){
610             if (array == null) {
611                 sb.append("null");
612                 return;
613             }
614             sb.append('[');
615             for(int i=0; i<array.length; i++){
616                 if (i>0){
617                     sb.append(',');
618                 }
619                 sb.append(array[i]);
620             }
621             sb.append(']');
622         }
623         private void arrayToString(StringBuffer sb, String[] array){
624             if (array == null) {
625                 sb.append("null");
626                 return;
627             }
628             sb.append('[');
629             for(int i=0; i<array.length; i++){
630                 if (i>0){
631                     sb.append(',');
632                 }
633                 sb.append(array[i]);
634             }
635             sb.append(']');
636         }
637     }
638 }