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 */
017
018package org.apache.commons.dbcp2;
019
020import java.sql.CallableStatement;
021import java.sql.Connection;
022import java.sql.PreparedStatement;
023import java.sql.SQLException;
024import java.util.NoSuchElementException;
025
026import org.apache.commons.pool2.KeyedObjectPool;
027import org.apache.commons.pool2.KeyedPooledObjectFactory;
028import org.apache.commons.pool2.PooledObject;
029import org.apache.commons.pool2.impl.DefaultPooledObject;
030
031/**
032 * A {@link DelegatingConnection} that pools {@link PreparedStatement}s.
033 * <p>
034 * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than
035 * creating a new PreparedStatement each time, may actually pull the statement
036 * from a pool of unused statements.
037 * The {@link PreparedStatement#close} method of the returned statement doesn't
038 * actually close the statement, but rather returns it to the pool.
039 * (See {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.)
040 *
041 *
042 * @see PoolablePreparedStatement
043 * @author Rodney Waldhoff
044 * @author Dirk Verbeeck
045 * @since 2.0
046 */
047public class PoolingConnection extends DelegatingConnection<Connection>
048        implements KeyedPooledObjectFactory<PStmtKey,DelegatingPreparedStatement> {
049
050    /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
051    private KeyedObjectPool<PStmtKey,DelegatingPreparedStatement> _pstmtPool = null;
052
053    /**
054     * Constructor.
055     * @param c the underlying {@link Connection}.
056     */
057    public PoolingConnection(final Connection c) {
058        super(c);
059    }
060
061
062    public void setStatementPool(
063            final KeyedObjectPool<PStmtKey,DelegatingPreparedStatement> pool) {
064        _pstmtPool = pool;
065    }
066
067
068    /**
069     * Close and free all {@link PreparedStatement}s or
070     * {@link CallableStatement}s from the pool, and close the underlying
071     * connection.
072     */
073    @Override
074    public synchronized void close() throws SQLException {
075        try {
076            if (null != _pstmtPool) {
077                final KeyedObjectPool<PStmtKey,DelegatingPreparedStatement> oldpool = _pstmtPool;
078                _pstmtPool = null;
079                try {
080                    oldpool.close();
081                } catch(final RuntimeException e) {
082                    throw e;
083                } catch(final Exception e) {
084                    throw new SQLException("Cannot close connection", e);
085                }
086            }
087        } finally {
088            try {
089                getDelegateInternal().close();
090            } finally {
091                setClosedInternal(true);
092            }
093        }
094    }
095
096    /**
097     * Create or obtain a {@link PreparedStatement} from the pool.
098     * @param sql the sql string used to define the PreparedStatement
099     * @return a {@link PoolablePreparedStatement}
100     */
101    @Override
102    public PreparedStatement prepareStatement(final String sql) throws SQLException {
103        if (null == _pstmtPool) {
104            throw new SQLException(
105                    "Statement pool is null - closed or invalid PoolingConnection.");
106        }
107        try {
108            return _pstmtPool.borrowObject(createKey(sql));
109        } catch(final NoSuchElementException e) {
110            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
111        } catch(final RuntimeException e) {
112            throw e;
113        } catch(final Exception e) {
114            throw new SQLException("Borrow prepareStatement from pool failed", e);
115        }
116    }
117
118    @Override
119    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
120        if (null == _pstmtPool) {
121            throw new SQLException(
122                    "Statement pool is null - closed or invalid PoolingConnection.");
123        }
124        try {
125            return _pstmtPool.borrowObject(createKey(sql, autoGeneratedKeys));
126        }
127        catch (final NoSuchElementException e) {
128            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
129        }
130        catch (final RuntimeException e) {
131            throw e;
132        }
133        catch (final Exception e) {
134            throw new SQLException("Borrow prepareStatement from pool failed", e);
135        }
136    }
137
138    /**
139     * Create or obtain a {@link PreparedStatement} from the pool.
140     * @param sql the sql string used to define the PreparedStatement
141     * @param resultSetType result set type
142     * @param resultSetConcurrency result set concurrency
143     * @return a {@link PoolablePreparedStatement}
144     */
145    @Override
146    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException {
147        if (null == _pstmtPool) {
148            throw new SQLException(
149                    "Statement pool is null - closed or invalid PoolingConnection.");
150        }
151        try {
152            return _pstmtPool.borrowObject(createKey(sql,resultSetType,resultSetConcurrency));
153        } catch(final NoSuchElementException e) {
154            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
155        } catch(final RuntimeException e) {
156            throw e;
157        } catch(final Exception e) {
158            throw new SQLException("Borrow prepareStatement from pool failed", e);
159        }
160    }
161
162    /**
163     * Create or obtain a {@link CallableStatement} from the pool.
164     * @param sql the sql string used to define the CallableStatement
165     * @return a {@link PoolableCallableStatement}
166     * @throws SQLException
167     */
168    @Override
169    public CallableStatement prepareCall(final String sql) throws SQLException {
170        try {
171            return (CallableStatement) _pstmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT));
172        } catch (final NoSuchElementException e) {
173            throw new SQLException("MaxOpenCallableStatements limit reached", e);
174        } catch (final RuntimeException e) {
175            throw e;
176        } catch (final Exception e) {
177            throw new SQLException("Borrow callableStatement from pool failed", e);
178        }
179    }
180
181    /**
182     * Create or obtain a {@link CallableStatement} from the pool.
183     * @param sql the sql string used to define the CallableStatement
184     * @param resultSetType result set type
185     * @param resultSetConcurrency result set concurrency
186     * @return a {@link PoolableCallableStatement}
187     * @throws SQLException
188     */
189    @Override
190    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException {
191        try {
192            return (CallableStatement) _pstmtPool.borrowObject(createKey(sql, resultSetType,
193                            resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
194        } catch (final NoSuchElementException e) {
195            throw new SQLException("MaxOpenCallableStatements limit reached", e);
196        } catch (final RuntimeException e) {
197            throw e;
198        } catch (final Exception e) {
199            throw new SQLException("Borrow callableStatement from pool failed", e);
200        }
201    }
202
203    /**
204     * Create or obtain a {@link PreparedStatement} from the pool.
205     * @param sql the sql string used to define the PreparedStatement
206     * @param resultSetType result set type
207     * @param resultSetConcurrency result set concurrency
208     * @param resultSetHoldability result set holdability
209     * @return a {@link PoolablePreparedStatement}
210     */
211    @Override
212    public PreparedStatement prepareStatement(final String sql, final int resultSetType,
213            final int resultSetConcurrency, final int resultSetHoldability) throws SQLException {
214        if (null == _pstmtPool) {
215            throw new SQLException(
216                    "Statement pool is null - closed or invalid PoolingConnection.");
217        }
218        try {
219            return _pstmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
220        } catch(final NoSuchElementException e) {
221            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
222        } catch(final RuntimeException e) {
223            throw e;
224        } catch(final Exception e) {
225            throw new SQLException("Borrow prepareStatement from pool failed", e);
226        }
227    }
228
229    /**
230     * Create or obtain a {@link PreparedStatement} from the pool.
231     * @param sql the sql string used to define the PreparedStatement
232     * @param columnIndexes column indexes
233     * @return a {@link PoolablePreparedStatement}
234     */
235    @Override
236    public PreparedStatement prepareStatement(final String sql, final int columnIndexes[])
237            throws SQLException {
238        if (null == _pstmtPool) {
239            throw new SQLException(
240                    "Statement pool is null - closed or invalid PoolingConnection.");
241        }
242        try {
243            return _pstmtPool.borrowObject(createKey(sql, columnIndexes));
244        } catch(final NoSuchElementException e) {
245            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
246        } catch(final RuntimeException e) {
247            throw e;
248        } catch(final Exception e) {
249            throw new SQLException("Borrow prepareStatement from pool failed", e);
250        }
251    }
252
253    /**
254     * Create or obtain a {@link PreparedStatement} from the pool.
255     * @param sql the sql string used to define the PreparedStatement
256     * @param columnNames column names
257     * @return a {@link PoolablePreparedStatement}
258     */
259    @Override
260    public PreparedStatement prepareStatement(final String sql, final String columnNames[])
261            throws SQLException {
262        if (null == _pstmtPool) {
263            throw new SQLException(
264                    "Statement pool is null - closed or invalid PoolingConnection.");
265        }
266        try {
267            return _pstmtPool.borrowObject(createKey(sql, columnNames));
268        } catch(final NoSuchElementException e) {
269            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
270        } catch(final RuntimeException e) {
271            throw e;
272        } catch(final Exception e) {
273            throw new SQLException("Borrow prepareStatement from pool failed", e);
274        }
275    }
276
277    /**
278     * Create or obtain a {@link CallableStatement} from the pool.
279     * @param sql the sql string used to define the CallableStatement
280     * @param resultSetType result set type
281     * @param resultSetConcurrency result set concurrency
282     * @param resultSetHoldability result set holdability
283     * @return a {@link PoolableCallableStatement}
284     * @throws SQLException
285     */
286    @Override
287    public CallableStatement prepareCall(final String sql, final int resultSetType,
288            final int resultSetConcurrency, final int resultSetHoldability) throws SQLException {
289        try {
290            return (CallableStatement) _pstmtPool.borrowObject(createKey(sql, resultSetType,
291                            resultSetConcurrency, resultSetHoldability, StatementType.CALLABLE_STATEMENT));
292        } catch (final NoSuchElementException e) {
293            throw new SQLException("MaxOpenCallableStatements limit reached", e);
294        } catch (final RuntimeException e) {
295            throw e;
296        } catch (final Exception e) {
297            throw new SQLException("Borrow callableStatement from pool failed", e);
298        }
299    }
300
301    protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
302        String catalog = null;
303        try {
304            catalog = getCatalog();
305        } catch (final SQLException e) {
306            // Ignored
307        }
308        return new PStmtKey(normalizeSQL(sql), catalog, autoGeneratedKeys);
309    }
310
311    /**
312     * Create a PStmtKey for the given arguments.
313     * @param sql the sql string used to define the statement
314     * @param resultSetType result set type
315     * @param resultSetConcurrency result set concurrency
316     */
317    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
318        String catalog = null;
319        try {
320            catalog = getCatalog();
321        } catch (final SQLException e) {
322            // Ignored
323        }
324        return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency);
325    }
326
327    /**
328     * Create a PStmtKey for the given arguments.
329     * @param sql the sql string used to define the statement
330     * @param resultSetType result set type
331     * @param resultSetConcurrency result set concurrency
332     * @param stmtType statement type
333     */
334    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final StatementType stmtType) {
335        String catalog = null;
336        try {
337            catalog = getCatalog();
338        } catch (final SQLException e) {
339            // Ignored
340        }
341        return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency, stmtType);
342    }
343
344    /**
345     * Create a PStmtKey for the given arguments.
346     * @param sql the sql string used to define the statement
347     */
348    protected PStmtKey createKey(final String sql) {
349        String catalog = null;
350        try {
351            catalog = getCatalog();
352        } catch (final SQLException e) {
353            // Ignored
354        }
355        return new PStmtKey(normalizeSQL(sql), catalog);
356    }
357
358    /**
359     * Create a PStmtKey for the given arguments.
360     * @param sql the SQL string used to define the statement
361     * @param stmtType statement type
362     */
363    protected PStmtKey createKey(final String sql, final StatementType stmtType) {
364        String catalog = null;
365        try {
366            catalog = getCatalog();
367        } catch (final SQLException e) {
368            // Ignored
369        }
370        return new PStmtKey(normalizeSQL(sql), catalog, stmtType, null);
371    }
372
373    /**
374     * Create a PStmtKey for the given arguments.
375     * @param sql the sql string used to define the statement
376     * @param resultSetType result set type
377     * @param resultSetConcurrency result set concurrency
378     * @param resultSetHoldability result set holdability
379     */
380    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
381            final int resultSetHoldability) {
382        String catalog = null;
383        try {
384            catalog = getCatalog();
385        } catch (final SQLException e) {
386            // Ignored
387        }
388        return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency, resultSetHoldability);
389    }
390
391    /**
392     * Create a PStmtKey for the given arguments.
393     * @param sql the sql string used to define the statement
394     * @param resultSetType result set type
395     * @param resultSetConcurrency result set concurrency
396     * @param resultSetHoldability result set holdability
397     * @param stmtType statement type
398     */
399    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
400            final int resultSetHoldability, final StatementType stmtType) {
401        String catalog = null;
402        try {
403            catalog = getCatalog();
404        } catch (final SQLException e) {
405            // Ignored
406        }
407        return new PStmtKey(normalizeSQL(sql), catalog, resultSetType, resultSetConcurrency, resultSetHoldability,  stmtType);
408    }
409
410    /**
411     * Create a PStmtKey for the given arguments.
412     * @param sql the sql string used to define the statement
413     * @param columnIndexes column indexes
414     */
415    protected PStmtKey createKey(final String sql, final int columnIndexes[]) {
416        String catalog = null;
417        try {
418            catalog = getCatalog();
419        } catch (final SQLException e) {
420            // Ignored
421        }
422        return new PStmtKey(normalizeSQL(sql), catalog, columnIndexes);
423    }
424
425    /**
426     * Create a PStmtKey for the given arguments.
427     * @param sql the sql string used to define the statement
428     * @param columnNames column names
429     */
430    protected PStmtKey createKey(final String sql, final String columnNames[]) {
431        String catalog = null;
432        try {
433            catalog = getCatalog();
434        } catch (final SQLException e) {
435            // Ignored
436        }
437        return new PStmtKey(normalizeSQL(sql), catalog, columnNames);
438    }
439
440    /**
441     * Normalize the given SQL statement, producing a
442     * canonical form that is semantically equivalent to the original.
443     */
444    protected String normalizeSQL(final String sql) {
445        return sql.trim();
446    }
447
448    /**
449     * {@link KeyedPooledObjectFactory} method for creating
450     * {@link PoolablePreparedStatement}s or {@link PoolableCallableStatement}s.
451     * The <code>stmtType</code> field in the key determines whether
452     * a PoolablePreparedStatement or PoolableCallableStatement is created.
453     *
454     * @param key the key for the {@link PreparedStatement} to be created
455     * @see #createKey(String, int, int, StatementType)
456     */
457    @Override
458    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key)
459            throws Exception {
460        if(null == key) {
461            throw new IllegalArgumentException("Prepared statement key is null or invalid.");
462        }
463        if (key.getStmtType() == StatementType.PREPARED_STATEMENT ) {
464            final PreparedStatement statement = (PreparedStatement) key.createStatement(getDelegate());
465            @SuppressWarnings({"rawtypes", "unchecked"}) // Unable to find way to avoid this
466            final
467            PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, _pstmtPool, this);
468            return new DefaultPooledObject<DelegatingPreparedStatement>(pps);
469        }
470        final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate());
471        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, _pstmtPool, this);
472        return new DefaultPooledObject<DelegatingPreparedStatement>(pcs);
473    }
474
475    /**
476     * {@link KeyedPooledObjectFactory} method for destroying
477     * PoolablePreparedStatements and PoolableCallableStatements.
478     * Closes the underlying statement.
479     *
480     * @param key ignored
481     * @param p the wrapped pooled statement to be destroyed.
482     */
483    @Override
484    public void destroyObject(final PStmtKey key,
485            final PooledObject<DelegatingPreparedStatement> p)
486            throws Exception {
487        p.getObject().getInnermostDelegate().close();
488    }
489
490    /**
491     * {@link KeyedPooledObjectFactory} method for validating
492     * pooled statements. Currently always returns true.
493     *
494     * @param key ignored
495     * @param p ignored
496     * @return {@code true}
497     */
498    @Override
499    public boolean validateObject(final PStmtKey key,
500            final PooledObject<DelegatingPreparedStatement> p) {
501        return true;
502    }
503
504    /**
505     * {@link KeyedPooledObjectFactory} method for activating
506     * pooled statements.
507     *
508     * @param key ignored
509     * @param p wrapped pooled statement to be activated
510     */
511    @Override
512    public void activateObject(final PStmtKey key,
513            final PooledObject<DelegatingPreparedStatement> p) throws Exception {
514        p.getObject().activate();
515    }
516
517    /**
518     * {@link KeyedPooledObjectFactory} method for passivating
519     * {@link PreparedStatement}s or {@link CallableStatement}s.
520     * Invokes {@link PreparedStatement#clearParameters}.
521     *
522     * @param key ignored
523     * @param p a wrapped {@link PreparedStatement}
524     */
525    @Override
526    public void passivateObject(final PStmtKey key,
527            final PooledObject<DelegatingPreparedStatement> p) throws Exception {
528        final DelegatingPreparedStatement dps = p.getObject();
529        dps.clearParameters();
530        dps.passivate();
531    }
532
533    @Override
534    public String toString() {
535        if (_pstmtPool != null ) {
536            return "PoolingConnection: " + _pstmtPool.toString();
537        }
538        return "PoolingConnection: null";
539    }
540
541    /**
542     * The possible statement types.
543     * @since 2.0
544     */
545    protected static enum StatementType {
546        CALLABLE_STATEMENT,
547        PREPARED_STATEMENT
548    }
549}