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