    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.PreparedStatement;
    021import java.sql.ResultSet;
    022import java.sql.SQLException;
    023import java.util.ArrayList;
    024import java.util.List;
    025
    026import org.apache.commons.pool2.KeyedObjectPool;
    027
    028/**
    029 * A {@link DelegatingPreparedStatement} that cooperates with {@link PoolingConnection} to implement a pool of
    030 * {@link PreparedStatement}s.
    031 * <p>
    032 * My {@link #close} method returns me to my containing pool. (See {@link PoolingConnection}.)
    033 * </p>
    034 *
    035 * @param <K>
    036 *            the key type
    037 *
    038 * @see PoolingConnection
    039 * @since 2.0
    040 */
    041public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
    042
    043    /**
    044     * The {@link KeyedObjectPool} from which I was obtained.
    045     */
    046    private final KeyedObjectPool<K, PoolablePreparedStatement<K>> pool;
    047
    048    /**
    049     * My "key" as used by {@link KeyedObjectPool}.
    050     */
    051    private final K key;
    052
    053    private volatile boolean batchAdded = false;
    054
    055    /**
    056     * Constructor.
    057     *
    058     * @param stmt
    059     *            my underlying {@link PreparedStatement}
    060     * @param key
    061     *            my key" as used by {@link KeyedObjectPool}
    062     * @param pool
    063     *            the {@link KeyedObjectPool} from which I was obtained.
    064     * @param conn
    065     *            the {@link java.sql.Connection Connection} from which I was created
    066     */
    067    public PoolablePreparedStatement(final PreparedStatement stmt, final K key,
    068            final KeyedObjectPool<K, PoolablePreparedStatement<K>> pool, final DelegatingConnection<?> conn) {
    069        super(conn, stmt);
    070        this.pool = pool;
    071        this.key = key;
    072
    073        // Remove from trace now because this statement will be
    074        // added by the activate method.
    075        removeThisTrace(getConnectionInternal());
    076    }
    077
    078    /**
    079     * Add batch.
    080     */
    081    @Override
    082    public void addBatch() throws SQLException {
    083        super.addBatch();
    084        batchAdded = true;
    085    }
    086
    087    /**
    088     * Clear Batch.
    089     */
    090    @Override
    091    public void clearBatch() throws SQLException {
    092        batchAdded = false;
    093        super.clearBatch();
    094    }
    095
    096    /**
    097     * Return me to my pool.
    098     */
    099    @Override
    100    public void close() throws SQLException {
    101        // calling close twice should have no effect
    102        if (!isClosed()) {
    103            try {
    104                pool.returnObject(key, this);
    105            } catch (final SQLException e) {
    106                throw e;
    107            } catch (final RuntimeException e) {
    108                throw e;
    109            } catch (final Exception e) {
    110                throw new SQLException("Cannot close preparedstatement (return to pool failed)", e);
    111            }
    112        }
    113    }
    114
    115    @Override
    116    public void activate() throws SQLException {
    117        setClosedInternal(false);
    118        if (getConnectionInternal() != null) {
    119            getConnectionInternal().addTrace(this);
    120        }
    121        super.activate();
    122    }
    123
    124    @Override
    125    public void passivate() throws SQLException {
    126        // DBCP-372. clearBatch with throw an exception if called when the
    127        // connection is marked as closed.
    128        if (batchAdded) {
    129            clearBatch();
    130        }
    131        setClosedInternal(true);
    132        removeThisTrace(getConnectionInternal());
    133
    134        // The JDBC spec requires that a statement closes any open
    135        // ResultSet's when it is closed.
    136        // FIXME The PreparedStatement we're wrapping should handle this for us.
    137        // See bug 17301 for what could happen when ResultSets are closed twice.
    138        final List<AbandonedTrace> resultSetList = getTrace();
    139        if (resultSetList != null) {
    140            final List<Exception> thrownList = new ArrayList<>();
    141            final ResultSet[] resultSets = resultSetList.toArray(new ResultSet[resultSetList.size()]);
    142            for (final ResultSet resultSet : resultSets) {
    143                if (resultSet != null) {
    144                    try {
    145                        resultSet.close();
    146                    } catch (Exception e) {
    147                        thrownList.add(e);
    148                    }
    149                }
    150            }
    151            clearTrace();
    152            if (!thrownList.isEmpty()) {
    153                throw new SQLExceptionList(thrownList);
    154            }
    155        }
    156
    157        super.passivate();
    158    }
    159}