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