    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.List;
    024
    025import org.apache.commons.pool2.KeyedObjectPool;
    026
    027/**
    028 * A {@link DelegatingPreparedStatement} that cooperates with
    029 * {@link PoolingConnection} to implement a pool of {@link PreparedStatement}s.
    030 * <p>
    031 * My {@link #close} method returns me to my containing pool. (See {@link PoolingConnection}.)
    032 *
    033 * @param <K> the key type
    034 *
    035 * @see PoolingConnection
    036 * @author Rodney Waldhoff
    037 * @author Glenn L. Nielsen
    038 * @author James House
    039 * @author Dirk Verbeeck
    040 * @since 2.0
    041 */
    042public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
    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     * @param stmt my underlying {@link PreparedStatement}
    058     * @param key my key" as used by {@link KeyedObjectPool}
    059     * @param pool the {@link KeyedObjectPool} from which I was obtained.
    060     * @param conn the {@link java.sql.Connection Connection} from which I was created
    061     */
    062    public PoolablePreparedStatement(final PreparedStatement stmt, final K key,
    063            final KeyedObjectPool<K, PoolablePreparedStatement<K>> pool,
    064            final DelegatingConnection<?> conn) {
    065        super(conn, stmt);
    066        _pool = pool;
    067        _key = key;
    068
    069        // Remove from trace now because this statement will be
    070        // added by the activate method.
    071        if(getConnectionInternal() != null) {
    072            getConnectionInternal().removeTrace(this);
    073        }
    074    }
    075
    076    /**
    077     * Add batch.
    078     */
    079    @Override
    080    public void addBatch() throws SQLException {
    081        super.addBatch();
    082        batchAdded = true;
    083    }
    084
    085    /**
    086     * Clear Batch.
    087     */
    088    @Override
    089    public void clearBatch() throws SQLException {
    090        batchAdded = false;
    091        super.clearBatch();
    092    }
    093
    094    /**
    095     * Return me to my pool.
    096     */
    097    @Override
    098    public void close() throws SQLException {
    099        // calling close twice should have no effect
    100        if (!isClosed()) {
    101            try {
    102                _pool.returnObject(_key, this);
    103            } catch(final SQLException e) {
    104                throw e;
    105            } catch(final RuntimeException e) {
    106                throw e;
    107            } catch(final Exception e) {
    108                throw new SQLException("Cannot close preparedstatement (return to pool failed)", e);
    109            }
    110        }
    111    }
    112
    113    @Override
    114    public void activate() throws SQLException{
    115        setClosedInternal(false);
    116        if(getConnectionInternal() != null) {
    117            getConnectionInternal().addTrace(this);
    118        }
    119        super.activate();
    120    }
    121
    122    @Override
    123    public void passivate() throws SQLException {
    124        // DBCP-372. clearBatch with throw an exception if called when the
    125        // connection is marked as closed.
    126        if (batchAdded) {
    127            clearBatch();
    128        }
    129        setClosedInternal(true);
    130        if(getConnectionInternal() != null) {
    131            getConnectionInternal().removeTrace(this);
    132        }
    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> resultSets = getTrace();
    139        if( resultSets != null) {
    140            final ResultSet[] set = resultSets.toArray(new ResultSet[resultSets.size()]);
    141            for (final ResultSet element : set) {
    142                element.close();
    143            }
    144            clearTrace();
    145        }
    146
    147        super.passivate();
    148    }
    149}