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;
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    @Override
079    public void activate() throws SQLException {
080        setClosedInternal(false);
081        if (getConnectionInternal() != null) {
082            getConnectionInternal().addTrace(this);
083        }
084        super.activate();
085    }
086
087    /**
088     * Add batch.
089     */
090    @Override
091    public void addBatch() throws SQLException {
092        super.addBatch();
093        batchAdded = true;
094    }
095
096    /**
097     * Clear Batch.
098     */
099    @Override
100    public void clearBatch() throws SQLException {
101        batchAdded = false;
102        super.clearBatch();
103    }
104
105    /**
106     * Return me to my pool.
107     */
108    @Override
109    public void close() throws SQLException {
110        // calling close twice should have no effect
111        if (!isClosed()) {
112            try {
113                pool.returnObject(key, this);
114            } catch (final SQLException | RuntimeException e) {
115                throw e;
116            } catch (final Exception e) {
117                throw new SQLException("Cannot close preparedstatement (return to pool failed)", e);
118            }
119        }
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        removeThisTrace(getConnectionInternal());
131
132        // The JDBC spec requires that a statement closes any open
133        // ResultSet's when it is closed.
134        // FIXME The PreparedStatement we're wrapping should handle this for us.
135        // See bug 17301 for what could happen when ResultSets are closed twice.
136        final List<AbandonedTrace> resultSetList = getTrace();
137        if (resultSetList != null) {
138            final List<Exception> thrownList = new ArrayList<>();
139            final ResultSet[] resultSets = resultSetList.toArray(Utils.EMPTY_RESULT_SET_ARRAY);
140            for (final ResultSet resultSet : resultSets) {
141                if (resultSet != null) {
142                    try {
143                        resultSet.close();
144                    } catch (final Exception e) {
145                        thrownList.add(e);
146                    }
147                }
148            }
149            clearTrace();
150            if (!thrownList.isEmpty()) {
151                throw new SQLExceptionList(thrownList);
152            }
153        }
154
155        super.passivate();
156    }
157}