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    *      https://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  package org.apache.commons.dbcp2.cpdsadapter;
18  
19  import java.sql.CallableStatement;
20  import java.sql.Connection;
21  import java.sql.PreparedStatement;
22  import java.sql.ResultSet;
23  import java.sql.SQLException;
24  import java.sql.Statement;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  
29  import javax.sql.ConnectionEvent;
30  import javax.sql.ConnectionEventListener;
31  import javax.sql.PooledConnection;
32  import javax.sql.StatementEventListener;
33  
34  import org.apache.commons.dbcp2.DelegatingConnection;
35  import org.apache.commons.dbcp2.DelegatingPreparedStatement;
36  import org.apache.commons.dbcp2.Jdbc41Bridge;
37  import org.apache.commons.dbcp2.PStmtKey;
38  import org.apache.commons.dbcp2.PoolableCallableStatement;
39  import org.apache.commons.dbcp2.PoolablePreparedStatement;
40  import org.apache.commons.dbcp2.PoolingConnection.StatementType;
41  import org.apache.commons.dbcp2.Utils;
42  import org.apache.commons.pool2.KeyedObjectPool;
43  import org.apache.commons.pool2.KeyedPooledObjectFactory;
44  import org.apache.commons.pool2.PooledObject;
45  import org.apache.commons.pool2.impl.DefaultPooledObject;
46  
47  /**
48   * Implements {@link PooledConnection} that is returned by {@link DriverAdapterCPDS}.
49   *
50   * @since 2.0
51   */
52  final class PooledConnectionImpl
53          implements PooledConnection, KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
54  
55      private static final String CLOSED = "Attempted to use PooledConnection after closed() was called.";
56  
57      /**
58       * The JDBC database connection that represents the physical db connection.
59       */
60      private Connection connection;
61  
62      /**
63       * A DelegatingConnection used to create a PoolablePreparedStatementStub.
64       */
65      private final DelegatingConnection<?> delegatingConnection;
66  
67      /**
68       * The JDBC database logical connection.
69       */
70      private Connection logicalConnection;
71  
72      /**
73       * ConnectionEventListeners.
74       */
75      private final List<ConnectionEventListener> eventListeners;
76  
77      /**
78       * StatementEventListeners.
79       */
80      private final List<StatementEventListener> statementEventListeners = Collections.synchronizedList(new ArrayList<>());
81  
82      /**
83       * Flag set to true, once {@link #close()} is called.
84       */
85      private volatile boolean closed;
86  
87      /** My pool of {@link PreparedStatement}s. */
88      private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool;
89  
90      /**
91       * Controls access to the underlying connection.
92       */
93      private boolean accessToUnderlyingConnectionAllowed;
94  
95      /**
96       * Wraps a real connection.
97       *
98       * @param connection
99       *            the connection to be wrapped.
100      */
101     PooledConnectionImpl(final Connection connection) {
102         this.connection = connection;
103         if (connection instanceof DelegatingConnection) {
104             this.delegatingConnection = (DelegatingConnection<?>) connection;
105         } else {
106             this.delegatingConnection = new DelegatingConnection<>(connection);
107         }
108         eventListeners = Collections.synchronizedList(new ArrayList<>());
109         closed = false;
110     }
111 
112     /**
113      * My {@link KeyedPooledObjectFactory} method for activating {@link PreparedStatement}s.
114      *
115      * @param pooledObject Activates the underlying object.
116      */
117     @Override
118     public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
119             throws SQLException {
120         pooledObject.getObject().activate();
121     }
122 
123     /**
124      * {@inheritDoc}
125      */
126     @Override
127     public void addConnectionEventListener(final ConnectionEventListener listener) {
128         if (!eventListeners.contains(listener)) {
129             eventListeners.add(listener);
130         }
131     }
132 
133     @Override
134     public void addStatementEventListener(final StatementEventListener listener) {
135         if (!statementEventListeners.contains(listener)) {
136             statementEventListeners.add(listener);
137         }
138     }
139 
140     /**
141      * Throws an SQLException, if isClosed is true
142      */
143     private void assertOpen() throws SQLException {
144         if (closed || connection == null) {
145             throw new SQLException(CLOSED);
146         }
147     }
148 
149     /**
150      * Closes the physical connection and marks this {@link PooledConnection} so that it may not be used to
151      * generate any more logical {@link Connection}s.
152      *
153      * @throws SQLException
154      *             Thrown when an error occurs or the connection is already closed.
155      */
156     @Override
157     public void close() throws SQLException {
158         assertOpen();
159         closed = true;
160         try {
161             if (stmtPool != null) {
162                 try {
163                     stmtPool.close();
164                 } finally {
165                     stmtPool = null;
166                 }
167             }
168         } catch (final RuntimeException e) {
169             throw e;
170         } catch (final Exception e) {
171             throw new SQLException("Cannot close connection (return to pool failed)", e);
172         } finally {
173             try {
174                 connection.close();
175             } finally {
176                 connection = null;
177             }
178         }
179     }
180 
181     /**
182      * Creates a {@link PStmtKey} for the given arguments.
183      *
184      * @param sql
185      *            The SQL statement.
186      * @return a {@link PStmtKey} for the given arguments.
187      */
188     protected PStmtKey createKey(final String sql) {
189         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull());
190     }
191 
192     /**
193      * Creates a {@link PStmtKey} for the given arguments.
194      *
195      * @param sql
196      *            The SQL statement.
197      * @param autoGeneratedKeys
198      *            A flag indicating whether auto-generated keys should be returned; one of
199      *            {@link Statement#RETURN_GENERATED_KEYS} or {@link Statement#NO_GENERATED_KEYS}.
200      * @return a key to uniquely identify a prepared statement.
201      */
202     protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
203         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
204     }
205 
206     /**
207      * Creates a {@link PStmtKey} for the given arguments.
208      *
209      * @param sql
210      *            The SQL statement.
211      * @param resultSetType
212      *            A result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
213      *            {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
214      * @param resultSetConcurrency
215      *            A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
216      *            {@link ResultSet#CONCUR_UPDATABLE}.
217      * @return a key to uniquely identify a prepared statement.
218      */
219     protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
220         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency);
221     }
222 
223     /**
224      * Creates a {@link PStmtKey} for the given arguments.
225      *
226      * @param sql
227      *            The SQL statement.
228      * @param resultSetType
229      *            a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
230      *            {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
231      * @param resultSetConcurrency
232      *            A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
233      *            {@link ResultSet#CONCUR_UPDATABLE}
234      * @param resultSetHoldability
235      *            One of the following {@link ResultSet} constants: {@link ResultSet#HOLD_CURSORS_OVER_COMMIT}
236      *            or {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}.
237      * @return a key to uniquely identify a prepared statement.
238      */
239     protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {
240         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability);
241     }
242 
243     /**
244      * Creates a {@link PStmtKey} for the given arguments.
245      *
246      * @param sql
247      *            The SQL statement.
248      * @param resultSetType
249      *            a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
250      *            {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}
251      * @param resultSetConcurrency
252      *            A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
253      *            {@link ResultSet#CONCUR_UPDATABLE}.
254      * @param resultSetHoldability
255      *            One of the following {@link ResultSet} constants: {@link ResultSet#HOLD_CURSORS_OVER_COMMIT}
256      *            or {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}.
257      * @param statementType
258      *            The SQL statement type, prepared or callable.
259      * @return a key to uniquely identify a prepared statement.
260      * @since 2.4.0
261      */
262     protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability,
263         final StatementType statementType) {
264         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability, statementType);
265     }
266 
267     /**
268      * Creates a {@link PStmtKey} for the given arguments.
269      *
270      * @param sql
271      *            The SQL statement.
272      * @param resultSetType
273      *            A result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
274      *            {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
275      * @param resultSetConcurrency
276      *            A concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
277      *            {@link ResultSet#CONCUR_UPDATABLE}.
278      * @param statementType
279      *            The SQL statement type, prepared or callable.
280      * @return a key to uniquely identify a prepared statement.
281      * @since 2.4.0
282      */
283     protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final StatementType statementType) {
284         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType);
285     }
286 
287     /**
288      * Creates a {@link PStmtKey} for the given arguments.
289      *
290      * @param sql
291      *            The SQL statement.
292      * @param columnIndexes
293      *            An array of column indexes indicating the columns that should be returned from the inserted row or
294      *            rows.
295      * @return a key to uniquely identify a prepared statement.
296      */
297     protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
298         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
299     }
300 
301     /**
302      * Creates a {@link PStmtKey} for the given arguments.
303      *
304      * @param sql
305      *            The SQL statement.
306      * @param statementType
307      *            The SQL statement type, prepared or callable.
308      * @return a key to uniquely identify a prepared statement.
309      */
310     protected PStmtKey createKey(final String sql, final StatementType statementType) {
311         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), statementType);
312     }
313 
314     /**
315      * Creates a {@link PStmtKey} for the given arguments.
316      *
317      * @param sql
318      *            The SQL statement.
319      * @param columnNames
320      *            An array of column names indicating the columns that should be returned from the inserted row or rows.
321      * @return a key to uniquely identify a prepared statement.
322      */
323     protected PStmtKey createKey(final String sql, final String[] columnNames) {
324         return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnNames);
325     }
326 
327     /**
328      * My {@link KeyedPooledObjectFactory} method for destroying {@link PreparedStatement}s.
329      *
330      * @param key
331      *            ignored
332      * @param pooledObject
333      *            the wrapped {@link PreparedStatement} to be destroyed.
334      */
335     @Override
336     public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) throws SQLException {
337         if (pooledObject != null) {
338             @SuppressWarnings("resource")
339             final DelegatingPreparedStatement object = pooledObject.getObject();
340             if (object != null) {
341                 @SuppressWarnings("resource")
342                 final Statement innermostDelegate = object.getInnermostDelegate();
343                 if (innermostDelegate != null) {
344                     innermostDelegate.close();
345                 }
346             }
347         }
348     }
349 
350     /**
351      * Closes the physical connection and checks that the logical connection was closed as well.
352      */
353     @Override
354     protected void finalize() throws Throwable {
355         // Closing the Connection ensures that if anyone tries to use it,
356         // an error will occur.
357         Utils.close(connection, null);
358         // make sure the last connection is marked as closed
359         if (logicalConnection != null && !logicalConnection.isClosed()) {
360             throw new SQLException("PooledConnection was gc'ed, without its last Connection being closed.");
361         }
362     }
363 
364     private String getCatalogOrNull() {
365         try {
366             return connection == null ? null : connection.getCatalog();
367         } catch (final SQLException e) {
368             return null;
369         }
370     }
371 
372     /**
373      * Returns a JDBC connection.
374      *
375      * @return The database connection.
376      * @throws SQLException
377      *             if the connection is not open or the previous logical connection is still open
378      */
379     @Override
380     public Connection getConnection() throws SQLException {
381         assertOpen();
382         // make sure the last connection is marked as closed
383         if (logicalConnection != null && !logicalConnection.isClosed()) {
384             // should notify pool of error so the pooled connection can
385             // be removed !FIXME!
386             throw new SQLException("PooledConnection was reused, without its previous Connection being closed.");
387         }
388 
389         // the spec requires that this return a new Connection instance.
390         logicalConnection = new ConnectionImpl(this, connection, isAccessToUnderlyingConnectionAllowed());
391         return logicalConnection;
392     }
393 
394     private Connection getRawConnection() throws SQLException {
395         assertOpen();
396         return connection;
397     }
398 
399     private String getSchemaOrNull() {
400         try {
401             return connection == null ? null : Jdbc41Bridge.getSchema(connection);
402         } catch (final SQLException e) {
403             return null;
404         }
405     }
406 
407     /**
408      * Returns the value of the accessToUnderlyingConnectionAllowed property.
409      *
410      * @return true if access to the underlying is allowed, false otherwise.
411      */
412     public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
413         return this.accessToUnderlyingConnectionAllowed;
414     }
415 
416     /**
417      * My {@link KeyedPooledObjectFactory} method for creating {@link PreparedStatement}s.
418      *
419      * @param key
420      *            The key for the {@link PreparedStatement} to be created.
421      */
422     @SuppressWarnings("resource")
423     @Override
424     public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException {
425         if (null == key) {
426             throw new IllegalArgumentException("Prepared statement key is null or invalid.");
427         }
428         if (key.getStmtType() == StatementType.PREPARED_STATEMENT) {
429             final PreparedStatement statement = (PreparedStatement) key.createStatement(connection);
430             @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
431             final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, stmtPool,
432                     delegatingConnection);
433             return new DefaultPooledObject<>(pps);
434         }
435         final CallableStatement statement = (CallableStatement) key.createStatement(connection);
436         @SuppressWarnings("unchecked")
437         final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, stmtPool,
438                 (DelegatingConnection<Connection>) delegatingConnection);
439         return new DefaultPooledObject<>(pcs);
440     }
441 
442     /**
443      * Sends a connectionClosed event.
444      */
445     void notifyListeners() {
446         final ConnectionEvent event = new ConnectionEvent(this);
447         new ArrayList<>(eventListeners).forEach(listener -> listener.connectionClosed(event));
448     }
449 
450     /**
451      * My {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s. Currently, invokes
452      * {@link PreparedStatement#clearParameters}.
453      *
454      * @param key
455      *            ignored
456      * @param pooledObject
457      *            a wrapped {@link PreparedStatement}
458      */
459     @Override
460     public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
461             throws SQLException {
462         @SuppressWarnings("resource")
463         final DelegatingPreparedStatement dps = pooledObject.getObject();
464         dps.clearParameters();
465         dps.passivate();
466     }
467 
468     /**
469      * Creates or obtains a {@link CallableStatement} from my pool.
470      *
471      * @param sql
472      *            an SQL statement that may contain one or more '?' parameter placeholders. Typically, this statement is
473      *            specified using JDBC call escape syntax.
474      * @return a default {@link CallableStatement} object containing the pre-compiled SQL statement.
475      * @throws SQLException
476      *                Thrown if a database access error occurs or this method is called on a closed connection.
477      * @since 2.4.0
478      */
479     @SuppressWarnings("resource") // getRawConnection() does not allocate
480     CallableStatement prepareCall(final String sql) throws SQLException {
481         if (stmtPool == null) {
482             return getRawConnection().prepareCall(sql);
483         }
484         try {
485             return (CallableStatement) stmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT));
486         } catch (final RuntimeException e) {
487             throw e;
488         } catch (final Exception e) {
489             throw new SQLException("Borrow prepareCall from pool failed", e);
490         }
491     }
492 
493     /**
494      * Creates or obtains a {@link CallableStatement} from my pool.
495      *
496      * @param sql
497      *            a {@link String} object that is the SQL statement to be sent to the database; may contain on or
498      *            more '?' parameters.
499      * @param resultSetType
500      *            a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
501      *            {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
502      * @param resultSetConcurrency
503      *            a concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
504      *            {@link ResultSet#CONCUR_UPDATABLE}.
505      * @return a {@link CallableStatement} object containing the pre-compiled SQL statement that will produce
506      *         {@link ResultSet} objects with the given type and concurrency.
507      * @throws SQLException
508      *             Thrown if a database access error occurs, this method is called on a closed connection or the given
509      *             parameters are not {@link ResultSet} constants indicating type and concurrency.
510      * @since 2.4.0
511      */
512     @SuppressWarnings("resource") // getRawConnection() does not allocate
513     CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
514             throws SQLException {
515         if (stmtPool == null) {
516             return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency);
517         }
518         try {
519             return (CallableStatement) stmtPool.borrowObject(
520                     createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
521         } catch (final RuntimeException e) {
522             throw e;
523         } catch (final Exception e) {
524             throw new SQLException("Borrow prepareCall from pool failed", e);
525         }
526     }
527 
528     /**
529      * Creates or obtains a {@link CallableStatement} from my pool.
530      *
531      * @param sql
532      *            a {@link String} object that is the SQL statement to be sent to the database; may contain on or
533      *            more '?' parameters.
534      * @param resultSetType
535      *            one of the following {@link ResultSet} constants: {@link ResultSet#TYPE_FORWARD_ONLY},
536      *            {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
537      * @param resultSetConcurrency
538      *            one of the following {@link ResultSet} constants: {@link ResultSet#CONCUR_READ_ONLY} or
539      *            {@link ResultSet#CONCUR_UPDATABLE}.
540      * @param resultSetHoldability
541      *            one of the following {@link ResultSet} constants: {@link ResultSet#HOLD_CURSORS_OVER_COMMIT}
542      *            or {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}.
543      * @return a new {@link CallableStatement} object, containing the pre-compiled SQL statement, that will
544      *         generate {@link ResultSet} objects with the given type, concurrency, and holdability.
545      * @throws SQLException
546      *             Thrown if a database access error occurs, this method is called on a closed connection or the given
547      *             parameters are not {@link ResultSet} constants indicating type, concurrency, and holdability.
548      * @since 2.4.0
549      */
550     @SuppressWarnings("resource") // getRawConnection() does not allocate
551     CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
552             final int resultSetHoldability) throws SQLException {
553         if (stmtPool == null) {
554             return getRawConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
555         }
556         try {
557             return (CallableStatement) stmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency,
558                     resultSetHoldability, StatementType.CALLABLE_STATEMENT));
559         } catch (final RuntimeException e) {
560             throw e;
561         } catch (final Exception e) {
562             throw new SQLException("Borrow prepareCall from pool failed", e);
563         }
564     }
565 
566     /**
567      * Creates or obtains a {@link PreparedStatement} from my pool.
568      *
569      * @param sql the SQL statement.
570      * @return a {@link PoolablePreparedStatement}
571      * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
572      *         the borrow failed.
573      */
574     @SuppressWarnings("resource") // getRawConnection() does not allocate
575     PreparedStatement prepareStatement(final String sql) throws SQLException {
576         if (stmtPool == null) {
577             return getRawConnection().prepareStatement(sql);
578         }
579         try {
580             return stmtPool.borrowObject(createKey(sql));
581         } catch (final RuntimeException e) {
582             throw e;
583         } catch (final Exception e) {
584             throw new SQLException("Borrow prepareStatement from pool failed", e);
585         }
586     }
587 
588     /**
589      * Creates or obtains a {@link PreparedStatement} from my pool.
590      *
591      * @param sql
592      *            an SQL statement that may contain one or more '?' IN parameter placeholders.
593      * @param autoGeneratedKeys
594      *            a flag indicating whether auto-generated keys should be returned; one of
595      *            {@link Statement#RETURN_GENERATED_KEYS} or {@link Statement#NO_GENERATED_KEYS}.
596      * @return a {@link PoolablePreparedStatement}
597      * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
598      *         the borrow failed.
599      * @see Connection#prepareStatement(String, int)
600      */
601     @SuppressWarnings("resource") // getRawConnection() does not allocate
602     PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
603         if (stmtPool == null) {
604             return getRawConnection().prepareStatement(sql, autoGeneratedKeys);
605         }
606         try {
607             return stmtPool.borrowObject(createKey(sql, autoGeneratedKeys));
608         } catch (final RuntimeException e) {
609             throw e;
610         } catch (final Exception e) {
611             throw new SQLException("Borrow prepareStatement from pool failed", e);
612         }
613     }
614 
615     /**
616      * Creates or obtains a {@link PreparedStatement} from my pool.
617      *
618      * @param sql
619      *            a {@link String} object that is the SQL statement to be sent to the database; may contain one or
620      *            more '?' IN parameters.
621      * @param resultSetType
622      *            a result set type; one of {@link ResultSet#TYPE_FORWARD_ONLY},
623      *            {@link ResultSet#TYPE_SCROLL_INSENSITIVE}, or {@link ResultSet#TYPE_SCROLL_SENSITIVE}.
624      * @param resultSetConcurrency
625      *            a concurrency type; one of {@link ResultSet#CONCUR_READ_ONLY} or
626      *            {@link ResultSet#CONCUR_UPDATABLE}.
627      *
628      * @return a {@link PoolablePreparedStatement}.
629      * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
630      *         the borrow failed.
631      * @see Connection#prepareStatement(String, int, int)
632      */
633     @SuppressWarnings("resource") // getRawConnection() does not allocate
634     PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
635             throws SQLException {
636         if (stmtPool == null) {
637             return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency);
638         }
639         try {
640             return stmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency));
641         } catch (final RuntimeException e) {
642             throw e;
643         } catch (final Exception e) {
644             throw new SQLException("Borrow prepareStatement from pool failed", e);
645         }
646     }
647 
648     @SuppressWarnings("resource") // getRawConnection() does not allocate
649     PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
650             final int resultSetHoldability) throws SQLException {
651         if (stmtPool == null) {
652             return getRawConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
653         }
654         try {
655             return stmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
656         } catch (final RuntimeException e) {
657             throw e;
658         } catch (final Exception e) {
659             throw new SQLException("Borrow prepareStatement from pool failed", e);
660         }
661     }
662 
663     @SuppressWarnings("resource") // getRawConnection() does not allocate
664     PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
665         if (stmtPool == null) {
666             return getRawConnection().prepareStatement(sql, columnIndexes);
667         }
668         try {
669             return stmtPool.borrowObject(createKey(sql, columnIndexes));
670         } catch (final RuntimeException e) {
671             throw e;
672         } catch (final Exception e) {
673             throw new SQLException("Borrow prepareStatement from pool failed", e);
674         }
675     }
676 
677     @SuppressWarnings("resource") // getRawConnection() does not allocate
678     PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
679         if (stmtPool == null) {
680             return getRawConnection().prepareStatement(sql, columnNames);
681         }
682         try {
683             return stmtPool.borrowObject(createKey(sql, columnNames));
684         } catch (final RuntimeException e) {
685             throw e;
686         } catch (final Exception e) {
687             throw new SQLException("Borrow prepareStatement from pool failed", e);
688         }
689     }
690 
691     /**
692      * {@inheritDoc}
693      */
694     @Override
695     public void removeConnectionEventListener(final ConnectionEventListener listener) {
696         eventListeners.remove(listener);
697     }
698 
699     @Override
700     public void removeStatementEventListener(final StatementEventListener listener) {
701         statementEventListeners.remove(listener);
702     }
703 
704     /**
705      * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
706      * the underlying connection. (Default: false.)
707      *
708      * @param allow
709      *            Access to the underlying connection is granted when true.
710      */
711     public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
712         this.accessToUnderlyingConnectionAllowed = allow;
713     }
714 
715     public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> statementPool) {
716         stmtPool = statementPool;
717     }
718 
719     /**
720      * @since 2.6.0
721      */
722     @Override
723     public synchronized String toString() {
724         final StringBuilder builder = new StringBuilder(super.toString());
725         builder.append("[connection=");
726         builder.append(connection);
727         builder.append(", delegatingConnection=");
728         builder.append(delegatingConnection);
729         builder.append(", logicalConnection=");
730         builder.append(logicalConnection);
731         builder.append(", eventListeners=");
732         builder.append(eventListeners);
733         builder.append(", statementEventListeners=");
734         builder.append(statementEventListeners);
735         builder.append(", closed=");
736         builder.append(closed);
737         builder.append(", stmtPool=");
738         builder.append(stmtPool);
739         builder.append(", accessToUnderlyingConnectionAllowed=");
740         builder.append(accessToUnderlyingConnectionAllowed);
741         builder.append("]");
742         return builder.toString();
743     }
744 
745     /**
746      * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s.
747      *
748      * @param key
749      *            Ignored.
750      * @param pooledObject
751      *            Ignored.
752      * @return {@code true}
753      */
754     @Override
755     public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) {
756         return true;
757     }
758 }