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> thrown = 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                        thrown.add(e);
148                    }
149                }
150            }
151            clearTrace();
152            if (!thrown.isEmpty()) {
153                throw new SQLExceptionList(thrown);
154            }
155        }
156
157        super.passivate();
158    }
159}