001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbcp2;
018
019import java.sql.CallableStatement;
020import java.sql.Connection;
021import java.sql.PreparedStatement;
022import java.sql.SQLException;
023import java.sql.Statement;
024import java.util.NoSuchElementException;
025import java.util.Objects;
026
027import org.apache.commons.pool2.KeyedObjectPool;
028import org.apache.commons.pool2.KeyedPooledObjectFactory;
029import org.apache.commons.pool2.PooledObject;
030import org.apache.commons.pool2.impl.DefaultPooledObject;
031import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
032
033/**
034 * A {@link DelegatingConnection} that pools {@link PreparedStatement}s.
035 * <p>
036 * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement each
037 * time, may actually pull the statement from a pool of unused statements. The {@link PreparedStatement#close} method of
038 * the returned statement doesn't actually close the statement, but rather returns it to the pool. (See
039 * {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.)
040 * </p>
041 *
042 * @see PoolablePreparedStatement
043 * @since 2.0
044 */
045public class PoolingConnection extends DelegatingConnection<Connection>
046        implements KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
047
048    /**
049     * Statement types.
050     *
051     * See subclasses of {@link Statement}.
052     * 
053     * @since 2.0 protected enum.
054     * @since 2.4.0 public enum.
055     * @see Statement
056     * @see CallableStatement
057     * @see PreparedStatement
058     */
059    public enum StatementType {
060
061        /**
062         * Callable statement.
063         *
064         * @see CallableStatement
065         */
066        CALLABLE_STATEMENT,
067
068        /**
069         * Prepared statement.
070         *
071         * @see PreparedStatement
072         */
073        PREPARED_STATEMENT
074    }
075
076    /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
077    private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pStmtPool;
078
079    private boolean clearStatementPoolOnReturn;
080
081    /**
082     * Constructs a new instance.
083     *
084     * @param connection
085     *            the underlying {@link Connection}.
086     */
087    public PoolingConnection(final Connection connection) {
088        super(connection);
089    }
090
091    /**
092     * {@link KeyedPooledObjectFactory} method for activating pooled statements.
093     *
094     * @param key
095     *            ignored
096     * @param pooledObject
097     *            wrapped pooled statement to be activated
098     */
099    @Override
100    public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
101            throws SQLException {
102        pooledObject.getObject().activate();
103    }
104
105    /**
106     * Closes and frees all {@link PreparedStatement}s or {@link CallableStatement}s from the pool, and close the
107     * underlying connection.
108     */
109    @Override
110    public synchronized void close() throws SQLException {
111        try {
112            if (null != pStmtPool) {
113                final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> oldPool = pStmtPool;
114                pStmtPool = null;
115                try {
116                    oldPool.close();
117                } catch (final RuntimeException e) {
118                    throw e;
119                } catch (final Exception e) {
120                    throw new SQLException("Cannot close connection", e);
121                }
122            }
123        } finally {
124            try {
125                getDelegateInternal().close();
126            } finally {
127                setClosedInternal(true);
128            }
129        }
130    }
131
132    /**
133     * Notification from {@link PoolableConnection} that we returned to the pool.
134     *
135     * @throws SQLException when {@code clearStatementPoolOnReturn} is true and the statement pool could not be
136     *                      cleared
137     * @since 2.8.0
138     */
139    public void connectionReturnedToPool() throws SQLException {
140        if (pStmtPool != null && clearStatementPoolOnReturn) {
141            try {
142                pStmtPool.clear();
143            } catch (final Exception e) {
144                throw new SQLException("Error clearing statement pool", e);
145            }
146        }
147    }
148
149    /**
150     * Creates a PStmtKey for the given arguments.
151     *
152     * @param sql
153     *            the SQL string used to define the statement
154     *
155     * @return the PStmtKey created for the given arguments.
156     */
157    protected PStmtKey createKey(final String sql) {
158        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull());
159    }
160
161    /**
162     * Creates a PStmtKey for the given arguments.
163     *
164     * @param sql
165     *            the SQL string used to define the statement
166     * @param autoGeneratedKeys
167     *            A flag indicating whether auto-generated keys should be returned; one of
168     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
169     *
170     * @return the PStmtKey created for the given arguments.
171     */
172    protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
173        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
174    }
175
176    /**
177     * Creates a PStmtKey for the given arguments.
178     *
179     * @param sql
180     *            the SQL string used to define the statement
181     * @param resultSetType
182     *            result set type
183     * @param resultSetConcurrency
184     *            result set concurrency
185     *
186     * @return the PStmtKey created for the given arguments.
187     */
188    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
189        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency);
190    }
191
192    /**
193     * Creates a PStmtKey for the given arguments.
194     *
195     * @param sql
196     *            the SQL string used to define the statement
197     * @param resultSetType
198     *            result set type
199     * @param resultSetConcurrency
200     *            result set concurrency
201     * @param resultSetHoldability
202     *            result set holdability
203     *
204     * @return the PStmtKey created for the given arguments.
205     */
206    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
207            final int resultSetHoldability) {
208        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency,
209                resultSetHoldability);
210    }
211
212    /**
213     * Creates a PStmtKey for the given arguments.
214     *
215     * @param sql
216     *            the SQL string used to define the statement
217     * @param resultSetType
218     *            result set type
219     * @param resultSetConcurrency
220     *            result set concurrency
221     * @param resultSetHoldability
222     *            result set holdability
223     * @param statementType
224     *            statement type
225     *
226     * @return the PStmtKey created for the given arguments.
227     */
228    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
229            final int resultSetHoldability, final StatementType statementType) {
230        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency,
231                resultSetHoldability, statementType);
232    }
233
234    /**
235     * Creates a PStmtKey for the given arguments.
236     *
237     * @param sql
238     *            the SQL string used to define the statement
239     * @param resultSetType
240     *            result set type
241     * @param resultSetConcurrency
242     *            result set concurrency
243     * @param statementType
244     *            statement type
245     *
246     * @return the PStmtKey created for the given arguments.
247     */
248    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
249            final StatementType statementType) {
250        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType);
251    }
252
253    /**
254     * Creates a PStmtKey for the given arguments.
255     *
256     * @param sql
257     *            the SQL string used to define the statement
258     * @param columnIndexes
259     *            An array of column indexes indicating the columns that should be returned from the inserted row or
260     *            rows.
261     *
262     * @return the PStmtKey created for the given arguments.
263     */
264    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
265        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
266    }
267
268    /**
269     * Creates a PStmtKey for the given arguments.
270     *
271     * @param sql
272     *            the SQL string used to define the statement
273     * @param statementType
274     *            statement type
275     *
276     * @return the PStmtKey created for the given arguments.
277     */
278    protected PStmtKey createKey(final String sql, final StatementType statementType) {
279        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType, null);
280    }
281
282    /**
283     * Creates a PStmtKey for the given arguments.
284     *
285     * @param sql
286     *            the SQL string used to define the statement
287     * @param columnNames
288     *            column names
289     *
290     * @return the PStmtKey created for the given arguments.
291     */
292    protected PStmtKey createKey(final String sql, final String[] columnNames) {
293        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames);
294    }
295
296    /**
297     * {@link KeyedPooledObjectFactory} method for destroying PoolablePreparedStatements and PoolableCallableStatements.
298     * Closes the underlying statement.
299     *
300     * @param key
301     *            ignored
302     * @param pooledObject
303     *            the wrapped pooled statement to be destroyed.
304     */
305    @Override
306    public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) throws SQLException {
307        if (pooledObject != null) {
308            @SuppressWarnings("resource")
309            final DelegatingPreparedStatement object = pooledObject.getObject();
310            if (object != null) {
311                @SuppressWarnings("resource")
312                final Statement innermostDelegate = object.getInnermostDelegate();
313                if (innermostDelegate != null) {
314                    innermostDelegate.close();
315                }
316            }
317        }
318    }
319
320    private String getCatalogOrNull() {
321        try {
322            return getCatalog();
323        } catch (final SQLException ignored) {
324            return null;
325        }
326    }
327
328    private String getSchemaOrNull() {
329        try {
330            return getSchema();
331        } catch (final SQLException ignored) {
332            return null;
333        }
334    }
335
336    /**
337     * Gets the prepared statement pool.
338     *
339     * @return statement pool
340     * @since 2.8.0
341     */
342    public KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> getStatementPool() {
343        return pStmtPool;
344    }
345
346    /**
347     * {@link KeyedPooledObjectFactory} method for creating {@link PoolablePreparedStatement}s or
348     * {@link PoolableCallableStatement}s. The {@code stmtType} field in the key determines whether a
349     * PoolablePreparedStatement or PoolableCallableStatement is created.
350     *
351     * @param key
352     *            the key for the {@link PreparedStatement} to be created
353     * @see #createKey(String, int, int, StatementType)
354     */
355    @SuppressWarnings("resource")
356    @Override
357    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException {
358        if (null == key) {
359            throw new IllegalArgumentException("Prepared statement key is null or invalid.");
360        }
361        if (key.getStmtType() == StatementType.PREPARED_STATEMENT) {
362            final PreparedStatement statement = (PreparedStatement) key.createStatement(getDelegate());
363            @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
364            final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool, this);
365            return new DefaultPooledObject<>(pps);
366        }
367        final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate());
368        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool, this);
369        return new DefaultPooledObject<>(pcs);
370    }
371
372    /**
373     * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original.
374     *
375     * @param sql The statement to be normalized.
376     *
377     * @return The canonical form of the supplied SQL statement.
378     */
379    protected String normalizeSQL(final String sql) {
380        return sql.trim();
381    }
382
383    /**
384     * {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s or {@link CallableStatement}s.
385     * Invokes {@link PreparedStatement#clearParameters}.
386     *
387     * @param key
388     *            ignored
389     * @param pooledObject
390     *            a wrapped {@link PreparedStatement}
391     */
392    @Override
393    public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
394            throws SQLException {
395        @SuppressWarnings("resource")
396        final DelegatingPreparedStatement dps = pooledObject.getObject();
397        dps.clearParameters();
398        dps.passivate();
399    }
400
401    /**
402     * Creates or obtains a {@link CallableStatement} from the pool.
403     *
404     * @param key
405     *            a {@link PStmtKey} for the given arguments
406     * @return a {@link PoolableCallableStatement}
407     * @throws SQLException
408     *             Wraps an underlying exception.
409     */
410    private CallableStatement prepareCall(final PStmtKey key) throws SQLException {
411        return (CallableStatement) prepareStatement(key);
412    }
413
414    /**
415     * Creates or obtains a {@link CallableStatement} from the pool.
416     *
417     * @param sql
418     *            the SQL string used to define the CallableStatement
419     * @return a {@link PoolableCallableStatement}
420     * @throws SQLException
421     *             Wraps an underlying exception.
422     */
423    @Override
424    public CallableStatement prepareCall(final String sql) throws SQLException {
425        return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT));
426    }
427
428    /**
429     * Creates or obtains a {@link CallableStatement} from the pool.
430     *
431     * @param sql
432     *            the SQL string used to define the CallableStatement
433     * @param resultSetType
434     *            result set type
435     * @param resultSetConcurrency
436     *            result set concurrency
437     * @return a {@link PoolableCallableStatement}
438     * @throws SQLException
439     *             Wraps an underlying exception.
440     */
441    @Override
442    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
443            throws SQLException {
444        return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
445    }
446
447    /**
448     * Creates or obtains a {@link CallableStatement} from the pool.
449     *
450     * @param sql
451     *            the SQL string used to define the CallableStatement
452     * @param resultSetType
453     *            result set type
454     * @param resultSetConcurrency
455     *            result set concurrency
456     * @param resultSetHoldability
457     *            result set holdability
458     * @return a {@link PoolableCallableStatement}
459     * @throws SQLException
460     *             Wraps an underlying exception.
461     */
462    @Override
463    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
464            final int resultSetHoldability) throws SQLException {
465        return prepareCall(createKey(sql, resultSetType, resultSetConcurrency,
466                resultSetHoldability, StatementType.CALLABLE_STATEMENT));
467    }
468
469    /**
470     * Creates or obtains a {@link PreparedStatement} from the pool.
471     *
472     * @param key
473     *            a {@link PStmtKey} for the given arguments
474     * @return a {@link PoolablePreparedStatement}
475     * @throws SQLException
476     *             Wraps an underlying exception.
477     */
478    private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException {
479        if (null == pStmtPool) {
480            throw new SQLException("Statement pool is null - closed or invalid PoolingConnection.");
481        }
482        try {
483            return pStmtPool.borrowObject(key);
484        } catch (final NoSuchElementException e) {
485            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
486        } catch (final RuntimeException e) {
487            throw e;
488        } catch (final Exception e) {
489            throw new SQLException("Borrow prepareStatement from pool failed", e);
490        }
491    }
492
493    /**
494     * Creates or obtains a {@link PreparedStatement} from the pool.
495     *
496     * @param sql
497     *            the SQL string used to define the PreparedStatement
498     * @return a {@link PoolablePreparedStatement}
499     * @throws SQLException
500     *             Wraps an underlying exception.
501     */
502    @Override
503    public PreparedStatement prepareStatement(final String sql) throws SQLException {
504        return prepareStatement(createKey(sql));
505    }
506
507    /*
508     * Creates or obtains a {@link PreparedStatement} from the pool.
509     *
510     * @param sql
511     *            the SQL string used to define the PreparedStatement
512     * @param autoGeneratedKeys
513     *            A flag indicating whether auto-generated keys should be returned; one of
514     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
515     * @return a {@link PoolablePreparedStatement}
516     * @throws SQLException
517     *             Wraps an underlying exception.
518     */
519    @Override
520    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
521        return prepareStatement(createKey(sql, autoGeneratedKeys));
522    }
523
524    /**
525     * Creates or obtains a {@link PreparedStatement} from the pool.
526     *
527     * @param sql
528     *            the SQL string used to define the PreparedStatement
529     * @param resultSetType
530     *            result set type
531     * @param resultSetConcurrency
532     *            result set concurrency
533     * @return a {@link PoolablePreparedStatement}
534     * @throws SQLException
535     *             Wraps an underlying exception.
536     */
537    @Override
538    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
539            throws SQLException {
540        return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency));
541    }
542
543    /**
544     * Creates or obtains a {@link PreparedStatement} from the pool.
545     *
546     * @param sql
547     *            the SQL string used to define the PreparedStatement
548     * @param resultSetType
549     *            result set type
550     * @param resultSetConcurrency
551     *            result set concurrency
552     * @param resultSetHoldability
553     *            result set holdability
554     * @return a {@link PoolablePreparedStatement}
555     * @throws SQLException
556     *             Wraps an underlying exception.
557     */
558    @Override
559    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
560            final int resultSetHoldability) throws SQLException {
561        return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
562    }
563
564    /**
565     * Creates or obtains a {@link PreparedStatement} from the pool.
566     *
567     * @param sql
568     *            the SQL string used to define the PreparedStatement
569     * @param columnIndexes
570     *            An array of column indexes indicating the columns that should be returned from the inserted row or
571     *            rows.
572     * @return a {@link PoolablePreparedStatement}
573     * @throws SQLException
574     *             Wraps an underlying exception.
575     */
576    @Override
577    public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
578        return prepareStatement(createKey(sql, columnIndexes));
579    }
580
581    /**
582     * Creates or obtains a {@link PreparedStatement} from the pool.
583     *
584     * @param sql
585     *            the SQL string used to define the PreparedStatement
586     * @param columnNames
587     *            column names
588     * @return a {@link PoolablePreparedStatement}
589     * @throws SQLException
590     *             Wraps an underlying exception.
591     */
592    @Override
593    public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
594        return prepareStatement(createKey(sql, columnNames));
595    }
596
597    /**
598     * Sets whether the pool of statements should be cleared when the connection is returned to its pool.
599     * Default is false.
600     *
601     * @param clearStatementPoolOnReturn clear or not
602     * @since 2.8.0
603     */
604    public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) {
605        this.clearStatementPoolOnReturn = clearStatementPoolOnReturn;
606    }
607
608    /**
609     * Sets the prepared statement pool.
610     *
611     * @param pool
612     *            the prepared statement pool.
613     */
614    public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pool) {
615        pStmtPool = pool;
616    }
617
618    @Override
619    public synchronized String toString() {
620        if (pStmtPool instanceof GenericKeyedObjectPool) {
621            // DBCP-596 PoolingConnection.toString() causes StackOverflowError
622            final GenericKeyedObjectPool<?, ?> gkop = (GenericKeyedObjectPool<?, ?>) pStmtPool;
623            if (gkop.getFactory() == this) {
624                return "PoolingConnection: " + pStmtPool.getClass() + "@" + System.identityHashCode(pStmtPool);
625            }
626        }
627        return "PoolingConnection: " + Objects.toString(pStmtPool);
628    }
629
630    /**
631     * {@link KeyedPooledObjectFactory} method for validating pooled statements. Currently, always returns true.
632     *
633     * @param key
634     *            ignored
635     * @param pooledObject
636     *            ignored
637     * @return {@code true}
638     */
639    @Override
640    public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) {
641        return true;
642    }
643}