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: 836331 $ $Date: 2009-11-15 01:03:37 -0500 (Sun, 15 Nov 2009) $
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 DelegatingConnection delegatingConnection = null;
60  
61      /**
62       * The JDBC database logical connection.
63       */
64      private Connection logicalConnection = null;
65  
66      /**
67       * ConnectionEventListeners
68       */
69      private Vector eventListeners;
70  
71      /**
72       * StatementEventListeners
73       */
74      private Vector statementEventListeners = new Vector();
75  
76      /**
77       * flag set to true, once close() is called.
78       */
79      boolean isClosed;
80  
81      /** My pool of {*link PreparedStatement}s. */
82      protected KeyedObjectPool pstmtPool = null;
83  
84      /** 
85       * Controls access to the underlying connection 
86       */
87      private boolean accessToUnderlyingConnectionAllowed = false; 
88  
89      /**
90       * Wrap the real connection.
91       */
92      PooledConnectionImpl(Connection connection, KeyedObjectPool pool) {
93          this.connection = connection;
94          if (connection instanceof DelegatingConnection) {
95              this.delegatingConnection = (DelegatingConnection) connection;
96          } else {
97              this.delegatingConnection = new DelegatingConnection(connection);
98          }
99          eventListeners = new Vector();
100         isClosed = false;
101         if (pool != null) {
102             pstmtPool = pool;
103             pstmtPool.setFactory(this);            
104         }
105     }
106 
107     /**
108      * Add an event listener.
109      */
110     public void addConnectionEventListener(ConnectionEventListener listener) {
111         if (!eventListeners.contains(listener)) {
112             eventListeners.add(listener);
113         }
114     }
115 
116     /* JDBC_4_ANT_KEY_BEGIN */
117     public void addStatementEventListener(StatementEventListener listener) {
118         if (!statementEventListeners.contains(listener)) {
119             statementEventListeners.add(listener);
120         }
121     }
122     /* JDBC_4_ANT_KEY_END */
123 
124     /**
125      * Closes the physical connection and marks this 
126      * <code>PooledConnection</code> so that it may not be used 
127      * to generate any more logical <code>Connection</code>s.
128      *
129      * @exception SQLException if an error occurs
130      */
131     public void close() throws SQLException {        
132         assertOpen();
133         isClosed = true;
134         try {
135             if (pstmtPool != null) {
136                 try {
137                     pstmtPool.close();
138                 } finally {
139                     pstmtPool = null;
140                 }
141             }
142         } catch (RuntimeException e) {
143             throw e;
144         } catch (Exception e) {
145             throw new SQLNestedException("Cannot close connection (return to pool failed)", e);
146         } finally {
147             try {
148                 connection.close();
149             } finally {
150                 connection = null;
151             }
152         }
153     }
154 
155     /**
156      * Throws an SQLException, if isClosed() is true
157      */
158     private void assertOpen() throws SQLException {
159         if (isClosed) {
160             throw new SQLException(CLOSED);
161         }
162     }
163 
164     /**
165      * Returns a JDBC connection.
166      *
167      * @return The database connection.
168      */
169     public Connection getConnection() throws SQLException {
170         assertOpen();
171         // make sure the last connection is marked as closed
172         if (logicalConnection != null && !logicalConnection.isClosed()) {
173             // should notify pool of error so the pooled connection can
174             // be removed !FIXME!
175             throw new SQLException("PooledConnection was reused, without" 
176                     + "its previous Connection being closed.");
177         }
178 
179         // the spec requires that this return a new Connection instance.
180         logicalConnection = new ConnectionImpl(
181                 this, connection, isAccessToUnderlyingConnectionAllowed());
182         return logicalConnection;
183     }
184 
185     /**
186      * Remove an event listener.
187      */
188     public void removeConnectionEventListener(
189             ConnectionEventListener listener) {
190         eventListeners.remove(listener);
191     }
192 
193     /* JDBC_4_ANT_KEY_BEGIN */
194     public void removeStatementEventListener(StatementEventListener listener) {
195         statementEventListeners.remove(listener);
196     }
197     /* JDBC_4_ANT_KEY_END */
198 
199     /**
200      * Closes the physical connection and checks that the logical connection
201      * was closed as well.
202      */
203     protected void finalize() throws Throwable {
204         // Closing the Connection ensures that if anyone tries to use it,
205         // an error will occur.
206         try {
207             connection.close();
208         } catch (Exception ignored) {
209         }
210 
211         // make sure the last connection is marked as closed
212         if (logicalConnection != null && !logicalConnection.isClosed()) {
213             throw new SQLException("PooledConnection was gc'ed, without" 
214                     + "its last Connection being closed.");
215         }        
216     }
217 
218     /**
219      * sends a connectionClosed event.
220      */
221     void notifyListeners() {
222         ConnectionEvent event = new ConnectionEvent(this);
223         Object[] listeners = eventListeners.toArray();
224         for (int i = 0; i < listeners.length; i++) {
225             ((ConnectionEventListener) listeners[i]).connectionClosed(event);
226         }
227     }
228 
229     // -------------------------------------------------------------------
230     // The following code implements a PreparedStatement pool
231 
232     /**
233      * Create or obtain a {*link PreparedStatement} from my pool.
234      * @return a {*link PoolablePreparedStatement}
235      */
236     PreparedStatement prepareStatement(String sql) throws SQLException {
237         if (pstmtPool == null) {
238             return connection.prepareStatement(sql);
239         } else {
240             try {
241                 return (PreparedStatement) 
242                         pstmtPool.borrowObject(createKey(sql));
243             } catch (RuntimeException e) {
244                 throw e;
245             } catch (Exception e) {
246                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
247             }
248         }
249     }
250 
251     /**
252      * Create or obtain a {*link PreparedStatement} from my pool.
253      * @return a {*link PoolablePreparedStatement}
254      */
255     PreparedStatement prepareStatement(String sql, int resultSetType, 
256                                        int resultSetConcurrency) 
257             throws SQLException {
258         if (pstmtPool == null) {
259             return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
260         } else {
261             try {
262                 return (PreparedStatement) pstmtPool.borrowObject(
263                     createKey(sql,resultSetType,resultSetConcurrency));
264             } catch (RuntimeException e) {
265                 throw e;
266             } catch (Exception e) {
267                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
268             }
269         }
270     }
271 
272 /* JDBC_3_ANT_KEY_BEGIN */
273     /**
274      * Create or obtain a {*link PreparedStatement} from my pool.
275      * @return a {*link PoolablePreparedStatement}
276      */
277     PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) 
278             throws SQLException {
279         if (pstmtPool == null) {
280             return connection.prepareStatement(sql, autoGeneratedKeys);
281         } else {
282             try {
283                 return (PreparedStatement) pstmtPool.borrowObject(
284                     createKey(sql,autoGeneratedKeys));
285             } catch (RuntimeException e) {
286                 throw e;
287             } catch (Exception e) {
288                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
289             }
290         }
291     }
292 
293     PreparedStatement prepareStatement(String sql, int resultSetType,
294             int resultSetConcurrency, int resultSetHoldability)
295     throws SQLException {
296         if (pstmtPool == null) {
297             return connection.prepareStatement(sql, resultSetType,
298                     resultSetConcurrency, resultSetHoldability);
299         } else {
300             try {
301                 return (PreparedStatement) pstmtPool.borrowObject(
302                     createKey(sql, resultSetType, resultSetConcurrency,
303                             resultSetHoldability));
304             } catch (RuntimeException e) {
305                 throw e;
306             } catch (Exception e) {
307                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
308             }
309         }
310     }
311 
312     PreparedStatement prepareStatement(String sql, int columnIndexes[])
313     throws SQLException {
314         if (pstmtPool == null) {
315             return connection.prepareStatement(sql, columnIndexes);
316         } else {
317             try {
318                 return (PreparedStatement) pstmtPool.borrowObject(
319                     createKey(sql, columnIndexes));
320             } catch (RuntimeException e) {
321                 throw e;
322             } catch (Exception e) {
323                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
324             }
325         }
326     }
327 
328     PreparedStatement prepareStatement(String sql, String columnNames[])
329     throws SQLException {
330         if (pstmtPool == null) {
331             return connection.prepareStatement(sql, columnNames);
332         } else {
333             try {
334                 return (PreparedStatement) pstmtPool.borrowObject(
335                     createKey(sql, columnNames));
336             } catch (RuntimeException e) {
337                 throw e;
338             } catch (Exception e) {
339                 throw new SQLNestedException("Borrow prepareStatement from pool failed", e);
340             }
341         }
342     }
343 
344     /**
345      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
346      */
347     protected Object createKey(String sql, int autoGeneratedKeys) {
348         return new PStmtKey(normalizeSQL(sql), autoGeneratedKeys);
349     }
350 
351     /**
352      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
353      */
354     protected Object createKey(String sql, int resultSetType,
355             int resultSetConcurrency, int resultSetHoldability) {
356         return new PStmtKey(normalizeSQL(sql), resultSetType,
357                 resultSetConcurrency, resultSetHoldability);
358     }
359 
360     /**
361      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
362      */
363     protected Object createKey(String sql, int columnIndexes[]) {
364         return new PStmtKey(normalizeSQL(sql), columnIndexes);
365     }
366 
367     /**
368      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
369      */
370     protected Object createKey(String sql, String columnNames[]) {
371         return new PStmtKey(normalizeSQL(sql), columnNames);
372     }
373 
374 /* JDBC_3_ANT_KEY_END */
375 
376     /**
377      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
378      */
379     protected Object createKey(String sql, int resultSetType, 
380                                int resultSetConcurrency) {
381         return new PStmtKey(normalizeSQL(sql), resultSetType,
382                             resultSetConcurrency);
383     }
384     
385     /**
386      * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments.
387      */
388     protected Object createKey(String sql) {
389         return new PStmtKey(normalizeSQL(sql));
390     }
391 
392     /**
393      * Normalize the given SQL statement, producing a
394      * cannonical form that is semantically equivalent to the original.
395      */
396     protected String normalizeSQL(String sql) {
397         return sql.trim();
398     }
399 
400     /**
401      * My {*link KeyedPoolableObjectFactory} method for creating
402      * {*link PreparedStatement}s.
403      * @param obj the key for the {*link PreparedStatement} to be created
404      */
405     public Object makeObject(Object obj) throws Exception {
406         if (null == obj || !(obj instanceof PStmtKey)) {
407             throw new IllegalArgumentException();
408         } else {
409             // _openPstmts++;
410             PStmtKey key = (PStmtKey)obj;
411             if (null == key._resultSetType 
412                     && null == key._resultSetConcurrency) {
413                 if (null == key._autoGeneratedKeys) {
414                     return new PoolablePreparedStatementStub(
415                             connection.prepareStatement(key._sql),
416                             key, pstmtPool, delegatingConnection);
417                 } else {
418                     return new PoolablePreparedStatementStub(
419                             connection.prepareStatement(key._sql,
420                                     key._autoGeneratedKeys.intValue()),
421                             key, pstmtPool, delegatingConnection);
422                 }
423             } else {
424                 return new PoolablePreparedStatementStub(
425                         connection.prepareStatement(key._sql,
426                         key._resultSetType.intValue(),
427                         key._resultSetConcurrency.intValue()),
428                         key, pstmtPool, delegatingConnection);
429             }
430         }
431     }
432 
433     /**
434      * My {*link KeyedPoolableObjectFactory} method for destroying
435      * {*link PreparedStatement}s.
436      * @param key ignored
437      * @param obj the {*link PreparedStatement} to be destroyed.
438      */
439     public void destroyObject(Object key, Object obj) throws Exception {
440         //_openPstmts--;
441         if (obj instanceof DelegatingPreparedStatement) {
442             ((DelegatingPreparedStatement) obj).getInnermostDelegate().close();
443         } else {
444             ((PreparedStatement) obj).close();
445         }
446     }
447 
448     /**
449      * My {*link KeyedPoolableObjectFactory} method for validating
450      * {*link PreparedStatement}s.
451      * @param key ignored
452      * @param obj ignored
453      * @return <tt>true</tt>
454      */
455     public boolean validateObject(Object key, Object obj) {
456         return true;
457     }
458 
459     /**
460      * My {*link KeyedPoolableObjectFactory} method for activating
461      * {*link PreparedStatement}s.
462      * @param key ignored
463      * @param obj ignored
464      */
465     public void activateObject(Object key, Object obj) throws Exception {
466         ((PoolablePreparedStatementStub) obj).activate();
467     }
468 
469     /**
470      * My {*link KeyedPoolableObjectFactory} method for passivating
471      * {*link PreparedStatement}s.  Currently invokes {*link PreparedStatement#clearParameters}.
472      * @param key ignored
473      * @param obj a {*link PreparedStatement}
474      */
475     public void passivateObject(Object key, Object obj) throws Exception {
476         ((PreparedStatement) obj).clearParameters();
477         ((PoolablePreparedStatementStub) obj).passivate();
478     }
479 
480     /**
481      * Returns the value of the accessToUnderlyingConnectionAllowed property.
482      * 
483      * @return true if access to the underlying is allowed, false otherwise.
484      */
485     public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
486         return this.accessToUnderlyingConnectionAllowed;
487     }
488 
489     /**
490      * Sets the value of the accessToUnderlyingConnectionAllowed property.
491      * It controls if the PoolGuard allows access to the underlying connection.
492      * (Default: false)
493      * 
494      * @param allow Access to the underlying connection is granted when true.
495      */
496     public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
497         this.accessToUnderlyingConnectionAllowed = allow;
498     }
499     
500     /**
501      * A key uniquely identifying {*link PreparedStatement}s.
502      */
503     static class PStmtKey {
504         protected String _sql = null;
505         protected Integer _resultSetType = null;
506         protected Integer _resultSetConcurrency = null;
507         protected Integer _autoGeneratedKeys = null;
508         protected Integer _resultSetHoldability = null;
509         protected int _columnIndexes[] = null;
510         protected String _columnNames[] = null;
511         
512         PStmtKey(String sql) {
513             _sql = sql;
514         }
515 
516         PStmtKey(String sql, int resultSetType, int resultSetConcurrency) {
517             _sql = sql;
518             _resultSetType = new Integer(resultSetType);
519             _resultSetConcurrency = new Integer(resultSetConcurrency);
520         }
521 
522         PStmtKey(String sql, int autoGeneratedKeys) {
523             _sql = sql;
524             _autoGeneratedKeys = new Integer(autoGeneratedKeys);
525         }
526 
527         PStmtKey(String sql, int resultSetType, int resultSetConcurrency,
528                 int resultSetHoldability) {
529             _sql = sql;
530             _resultSetType = new Integer(resultSetType);
531             _resultSetConcurrency = new Integer(resultSetConcurrency);
532             _resultSetHoldability = new Integer(resultSetHoldability);
533         }
534 
535         PStmtKey(String sql, int columnIndexes[]) {
536             _sql = sql;
537             _columnIndexes = columnIndexes;
538         }
539 
540         PStmtKey(String sql, String columnNames[]) {
541             _sql = sql;
542             _columnNames = columnNames;
543         }
544 
545         
546         public boolean equals(Object that) {
547             try {
548                 PStmtKey key = (PStmtKey) that;
549                 return(((null == _sql && null == key._sql) || _sql.equals(key._sql)) &&
550                        ((null == _resultSetType && null == key._resultSetType) || _resultSetType.equals(key._resultSetType)) &&
551                        ((null == _resultSetConcurrency && null == key._resultSetConcurrency) || _resultSetConcurrency.equals(key._resultSetConcurrency)) &&
552                        ((null == _autoGeneratedKeys && null == key._autoGeneratedKeys) || _autoGeneratedKeys.equals(key._autoGeneratedKeys)) &&
553                        ((null == _resultSetHoldability && null == key._resultSetHoldability) || _resultSetHoldability.equals(key._resultSetHoldability)) &&
554                        ((null == _columnIndexes && null == key._columnIndexes) || Arrays.equals(_columnIndexes, key._columnIndexes)) &&
555                        ((null == _columnNames && null == key._columnNames) || Arrays.equals(_columnNames, key._columnNames))
556                       );
557             } catch (ClassCastException e) {
558                 return false;
559             } catch (NullPointerException e) {
560                 return false;
561             }
562         }
563 
564         public int hashCode() {
565             return(null == _sql ? 0 : _sql.hashCode());
566         }
567 
568         public String toString() {
569             StringBuffer buf = new StringBuffer();
570             buf.append("PStmtKey: sql=");
571             buf.append(_sql);
572             buf.append(", resultSetType=");
573             buf.append(_resultSetType);
574             buf.append(", resultSetConcurrency=");
575             buf.append(_resultSetConcurrency);
576             buf.append(", autoGeneratedKeys=");
577             buf.append(_autoGeneratedKeys);
578             buf.append(", resultSetHoldability=");
579             buf.append(_resultSetHoldability);
580             buf.append(", columnIndexes=");
581             buf.append(Arrays.toString(_columnIndexes));
582             buf.append(", columnNames=");
583             buf.append(Arrays.toString(_columnNames));
584             return buf.toString();
585         }
586     }
587 }