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 * @version $Id: PoolablePreparedStatement.java 1649430 2015-01-04 21:29:32Z tn $
041 * @since 2.0
042 */
043public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
044    /**
045     * The {@link KeyedObjectPool} from which I was obtained.
046     */
047    private final KeyedObjectPool<K,PoolablePreparedStatement<K>> _pool;
048
049    /**
050     * My "key" as used by {@link KeyedObjectPool}.
051     */
052    private final K _key;
053
054    private volatile boolean batchAdded = false;
055
056    /**
057     * Constructor
058     * @param stmt my underlying {@link PreparedStatement}
059     * @param key my key" as used by {@link KeyedObjectPool}
060     * @param pool the {@link KeyedObjectPool} from which I was obtained.
061     * @param conn the {@link java.sql.Connection Connection} from which I was created
062     */
063    public PoolablePreparedStatement(PreparedStatement stmt, K key,
064            KeyedObjectPool<K, PoolablePreparedStatement<K>> pool,
065            DelegatingConnection<?> conn) {
066        super(conn, stmt);
067        _pool = pool;
068        _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(SQLException e) {
105                throw e;
106            } catch(RuntimeException e) {
107                throw e;
108            } catch(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        List<AbandonedTrace> resultSets = getTrace();
140        if( resultSets != null) {
141            ResultSet[] set = resultSets.toArray(new ResultSet[resultSets.size()]);
142            for (ResultSet element : set) {
143                element.close();
144            }
145            clearTrace();
146        }
147
148        super.passivate();
149    }
150}