    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 */
    017package org.apache.commons.dbcp2;
    018
    019import java.sql.Connection;
    020import java.sql.ResultSet;
    021import java.sql.SQLException;
    022import java.sql.SQLWarning;
    023import java.sql.Statement;
    024import java.util.ArrayList;
    025import java.util.List;
    026
    027/**
    028 * A base delegating implementation of {@link Statement}.
    029 * <p>
    030 * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and
    031 * call the corresponding method on the "delegate" provided in my constructor.
    032 * <p>
    033 * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the
    034 * Statement ensures that the Connection which created it can close any open Statement's on Connection close.
    035 *
    036 * @since 2.0
    037 */
    038public class DelegatingStatement extends AbandonedTrace implements Statement {
    039
    040    /** My delegate. */
    041    private Statement statement;
    042
    043    /** The connection that created me. **/
    044    private DelegatingConnection<?> connection;
    045
    046    private boolean closed = false;
    047
    048    /**
    049     * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
    050     * which created it.
    051     *
    052     * @param statement
    053     *            the {@link Statement} to delegate all calls to.
    054     * @param connection
    055     *            the {@link DelegatingConnection} that created this statement.
    056     */
    057    public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) {
    058        super(connection);
    059        this.statement = statement;
    060        this.connection = connection;
    061    }
    062
    063    /**
    064     *
    065     * @throws SQLException
    066     *             thrown by the delegating statement.
    067     * @since 2.4.0 made public, was protected in 2.3.0.
    068     */
    069    public void activate() throws SQLException {
    070        if (statement instanceof DelegatingStatement) {
    071            ((DelegatingStatement) statement).activate();
    072        }
    073    }
    074
    075    @Override
    076    public void addBatch(final String sql) throws SQLException {
    077        checkOpen();
    078        try {
    079            statement.addBatch(sql);
    080        } catch (final SQLException e) {
    081            handleException(e);
    082        }
    083    }
    084
    085    @Override
    086    public void cancel() throws SQLException {
    087        checkOpen();
    088        try {
    089            statement.cancel();
    090        } catch (final SQLException e) {
    091            handleException(e);
    092        }
    093    }
    094
    095    protected void checkOpen() throws SQLException {
    096        if (isClosed()) {
    097            throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed.");
    098        }
    099    }
    100
    101    @Override
    102    public void clearBatch() throws SQLException {
    103        checkOpen();
    104        try {
    105            statement.clearBatch();
    106        } catch (final SQLException e) {
    107            handleException(e);
    108        }
    109    }
    110
    111    @Override
    112    public void clearWarnings() throws SQLException {
    113        checkOpen();
    114        try {
    115            statement.clearWarnings();
    116        } catch (final SQLException e) {
    117            handleException(e);
    118        }
    119    }
    120
    121    /**
    122     * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed.
    123     */
    124    @Override
    125    public void close() throws SQLException {
    126        if (isClosed()) {
    127            return;
    128        }
    129        final List<Exception> thrown = new ArrayList<>();
    130        try {
    131            if (connection != null) {
    132                connection.removeTrace(this);
    133                connection = null;
    134            }
    135
    136            // The JDBC spec requires that a statement close any open
    137            // ResultSet's when it is closed.
    138            // FIXME The PreparedStatement we're wrapping should handle this for us.
    139            // See bug 17301 for what could happen when ResultSets are closed twice.
    140            final List<AbandonedTrace> resultSetList = getTrace();
    141            if (resultSetList != null) {
    142                final int size = resultSetList.size();
    143                final ResultSet[] resultSets = resultSetList.toArray(new ResultSet[size]);
    144                for (final ResultSet resultSet : resultSets) {
    145                    if (resultSet != null) {
    146                        try {
    147                            resultSet.close();
    148                        } catch (Exception e) {
    149                            if (connection != null) {
    150                                // Does not rethrow e.
    151                                connection.handleExceptionNoThrow(e);
    152                            }
    153                            thrown.add(e);
    154                        }
    155                    }
    156                }
    157                clearTrace();
    158            }
    159            if (statement != null) {
    160                try {
    161                    statement.close();
    162                } catch (Exception e) {
    163                    if (connection != null) {
    164                        // Does not rethrow e.
    165                        connection.handleExceptionNoThrow(e);
    166                    }
    167                    thrown.add(e);
    168                }
    169            }
    170        } finally {
    171            closed = true;
    172            statement = null;
    173            if (!thrown.isEmpty()) {
    174                throw new SQLExceptionList(thrown);
    175            }
    176        }
    177    }
    178
    179    @Override
    180    public void closeOnCompletion() throws SQLException {
    181        checkOpen();
    182        try {
    183            Jdbc41Bridge.closeOnCompletion(statement);
    184        } catch (final SQLException e) {
    185            handleException(e);
    186        }
    187    }
    188
    189    @Override
    190    public boolean execute(final String sql) throws SQLException {
    191        checkOpen();
    192        setLastUsedInParent();
    193        try {
    194            return statement.execute(sql);
    195        } catch (final SQLException e) {
    196            handleException(e);
    197            return false;
    198        }
    199    }
    200
    201    @Override
    202    public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
    203        checkOpen();
    204        setLastUsedInParent();
    205        try {
    206            return statement.execute(sql, autoGeneratedKeys);
    207        } catch (final SQLException e) {
    208            handleException(e);
    209            return false;
    210        }
    211    }
    212
    213    @Override
    214    public boolean execute(final String sql, final int columnIndexes[]) throws SQLException {
    215        checkOpen();
    216        setLastUsedInParent();
    217        try {
    218            return statement.execute(sql, columnIndexes);
    219        } catch (final SQLException e) {
    220            handleException(e);
    221            return false;
    222        }
    223    }
    224
    225    @Override
    226    public boolean execute(final String sql, final String columnNames[]) throws SQLException {
    227        checkOpen();
    228        setLastUsedInParent();
    229        try {
    230            return statement.execute(sql, columnNames);
    231        } catch (final SQLException e) {
    232            handleException(e);
    233            return false;
    234        }
    235    }
    236
    237    @Override
    238    public int[] executeBatch() throws SQLException {
    239        checkOpen();
    240        setLastUsedInParent();
    241        try {
    242            return statement.executeBatch();
    243        } catch (final SQLException e) {
    244            handleException(e);
    245            throw new AssertionError();
    246        }
    247    }
    248
    249    /**
    250     * @since 2.5.0
    251     */
    252    @Override
    253    public long[] executeLargeBatch() throws SQLException {
    254        checkOpen();
    255        setLastUsedInParent();
    256        try {
    257            return statement.executeLargeBatch();
    258        } catch (final SQLException e) {
    259            handleException(e);
    260            return null;
    261        }
    262    }
    263
    264    /**
    265     * @since 2.5.0
    266     */
    267    @Override
    268    public long executeLargeUpdate(final String sql) throws SQLException {
    269        checkOpen();
    270        setLastUsedInParent();
    271        try {
    272            return statement.executeLargeUpdate(sql);
    273        } catch (final SQLException e) {
    274            handleException(e);
    275            return 0;
    276        }
    277    }
    278
    279    /**
    280     * @since 2.5.0
    281     */
    282    @Override
    283    public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
    284        checkOpen();
    285        setLastUsedInParent();
    286        try {
    287            return statement.executeLargeUpdate(sql, autoGeneratedKeys);
    288        } catch (final SQLException e) {
    289            handleException(e);
    290            return 0;
    291        }
    292    }
    293
    294    /**
    295     * @since 2.5.0
    296     */
    297    @Override
    298    public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
    299        checkOpen();
    300        setLastUsedInParent();
    301        try {
    302            return statement.executeLargeUpdate(sql, columnIndexes);
    303        } catch (final SQLException e) {
    304            handleException(e);
    305            return 0;
    306        }
    307    }
    308
    309    /**
    310     * @since 2.5.0
    311     */
    312    @Override
    313    public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException {
    314        checkOpen();
    315        setLastUsedInParent();
    316        try {
    317            return statement.executeLargeUpdate(sql, columnNames);
    318        } catch (final SQLException e) {
    319            handleException(e);
    320            return 0;
    321        }
    322    }
    323
    324    @Override
    325    public ResultSet executeQuery(final String sql) throws SQLException {
    326        checkOpen();
    327        setLastUsedInParent();
    328        try {
    329            return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql));
    330        } catch (final SQLException e) {
    331            handleException(e);
    332            throw new AssertionError();
    333        }
    334    }
    335
    336    @Override
    337    public int executeUpdate(final String sql) throws SQLException {
    338        checkOpen();
    339        setLastUsedInParent();
    340        try {
    341            return statement.executeUpdate(sql);
    342        } catch (final SQLException e) {
    343            handleException(e);
    344            return 0;
    345        }
    346    }
    347
    348    @Override
    349    public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
    350        checkOpen();
    351        setLastUsedInParent();
    352        try {
    353            return statement.executeUpdate(sql, autoGeneratedKeys);
    354        } catch (final SQLException e) {
    355            handleException(e);
    356            return 0;
    357        }
    358    }
    359
    360    @Override
    361    public int executeUpdate(final String sql, final int columnIndexes[]) throws SQLException {
    362        checkOpen();
    363        setLastUsedInParent();
    364        try {
    365            return statement.executeUpdate(sql, columnIndexes);
    366        } catch (final SQLException e) {
    367            handleException(e);
    368            return 0;
    369        }
    370    }
    371
    372    @Override
    373    public int executeUpdate(final String sql, final String columnNames[]) throws SQLException {
    374        checkOpen();
    375        setLastUsedInParent();
    376        try {
    377            return statement.executeUpdate(sql, columnNames);
    378        } catch (final SQLException e) {
    379            handleException(e);
    380            return 0;
    381        }
    382    }
    383
    384    @Override
    385    protected void finalize() throws Throwable {
    386        // This is required because of statement pooling. The poolable
    387        // statements will always be strongly held by the statement pool. If the
    388        // delegating statements that wrap the poolable statement are not
    389        // strongly held they will be garbage collected but at that point the
    390        // poolable statements need to be returned to the pool else there will
    391        // be a leak of statements from the pool. Closing this statement will
    392        // close all the wrapped statements and return any poolable statements
    393        // to the pool.
    394        close();
    395        super.finalize();
    396    }
    397
    398    @Override
    399    public Connection getConnection() throws SQLException {
    400        checkOpen();
    401        return getConnectionInternal(); // return the delegating connection that created this
    402    }
    403
    404    protected DelegatingConnection<?> getConnectionInternal() {
    405        return connection;
    406    }
    407
    408    /**
    409     * Returns my underlying {@link Statement}.
    410     *
    411     * @return my underlying {@link Statement}.
    412     * @see #getInnermostDelegate
    413     */
    414    public Statement getDelegate() {
    415        return statement;
    416    }
    417
    418    @Override
    419    public int getFetchDirection() throws SQLException {
    420        checkOpen();
    421        try {
    422            return statement.getFetchDirection();
    423        } catch (final SQLException e) {
    424            handleException(e);
    425            return 0;
    426        }
    427    }
    428
    429    @Override
    430    public int getFetchSize() throws SQLException {
    431        checkOpen();
    432        try {
    433            return statement.getFetchSize();
    434        } catch (final SQLException e) {
    435            handleException(e);
    436            return 0;
    437        }
    438    }
    439
    440    @Override
    441    public ResultSet getGeneratedKeys() throws SQLException {
    442        checkOpen();
    443        try {
    444            return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys());
    445        } catch (final SQLException e) {
    446            handleException(e);
    447            throw new AssertionError();
    448        }
    449    }
    450
    451    /**
    452     * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively
    453     * invokes this method on my delegate.
    454     * <p>
    455     * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when
    456     * no non-{@code DelegatingStatement} delegate can be found by traversing this chain.
    457     * </p>
    458     * <p>
    459     * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain
    460     * a "genuine" {@link Statement}.
    461     * </p>
    462     *
    463     * @return The innermost delegate.
    464     *
    465     * @see #getDelegate
    466     */
    467    @SuppressWarnings("resource")
    468    public Statement getInnermostDelegate() {
    469        Statement s = statement;
    470        while (s != null && s instanceof DelegatingStatement) {
    471            s = ((DelegatingStatement) s).getDelegate();
    472            if (this == s) {
    473                return null;
    474            }
    475        }
    476        return s;
    477    }
    478
    479    /**
    480     * @since 2.5.0
    481     */
    482    @Override
    483    public long getLargeMaxRows() throws SQLException {
    484        checkOpen();
    485        try {
    486            return statement.getLargeMaxRows();
    487        } catch (final SQLException e) {
    488            handleException(e);
    489            return 0;
    490        }
    491    }
    492
    493    /**
    494     * @since 2.5.0
    495     */
    496    @Override
    497    public long getLargeUpdateCount() throws SQLException {
    498        checkOpen();
    499        try {
    500            return statement.getLargeUpdateCount();
    501        } catch (final SQLException e) {
    502            handleException(e);
    503            return 0;
    504        }
    505    }
    506
    507    @Override
    508    public int getMaxFieldSize() throws SQLException {
    509        checkOpen();
    510        try {
    511            return statement.getMaxFieldSize();
    512        } catch (final SQLException e) {
    513            handleException(e);
    514            return 0;
    515        }
    516    }
    517
    518    @Override
    519    public int getMaxRows() throws SQLException {
    520        checkOpen();
    521        try {
    522            return statement.getMaxRows();
    523        } catch (final SQLException e) {
    524            handleException(e);
    525            return 0;
    526        }
    527    }
    528
    529    @Override
    530    public boolean getMoreResults() throws SQLException {
    531        checkOpen();
    532        try {
    533            return statement.getMoreResults();
    534        } catch (final SQLException e) {
    535            handleException(e);
    536            return false;
    537        }
    538    }
    539
    540    @Override
    541    public boolean getMoreResults(final int current) throws SQLException {
    542        checkOpen();
    543        try {
    544            return statement.getMoreResults(current);
    545        } catch (final SQLException e) {
    546            handleException(e);
    547            return false;
    548        }
    549    }
    550
    551    @Override
    552    public int getQueryTimeout() throws SQLException {
    553        checkOpen();
    554        try {
    555            return statement.getQueryTimeout();
    556        } catch (final SQLException e) {
    557            handleException(e);
    558            return 0;
    559        }
    560    }
    561
    562    @Override
    563    public ResultSet getResultSet() throws SQLException {
    564        checkOpen();
    565        try {
    566            return DelegatingResultSet.wrapResultSet(this, statement.getResultSet());
    567        } catch (final SQLException e) {
    568            handleException(e);
    569            throw new AssertionError();
    570        }
    571    }
    572
    573    @Override
    574    public int getResultSetConcurrency() throws SQLException {
    575        checkOpen();
    576        try {
    577            return statement.getResultSetConcurrency();
    578        } catch (final SQLException e) {
    579            handleException(e);
    580            return 0;
    581        }
    582    }
    583
    584    @Override
    585    public int getResultSetHoldability() throws SQLException {
    586        checkOpen();
    587        try {
    588            return statement.getResultSetHoldability();
    589        } catch (final SQLException e) {
    590            handleException(e);
    591            return 0;
    592        }
    593    }
    594
    595    @Override
    596    public int getResultSetType() throws SQLException {
    597        checkOpen();
    598        try {
    599            return statement.getResultSetType();
    600        } catch (final SQLException e) {
    601            handleException(e);
    602            return 0;
    603        }
    604    }
    605
    606    @Override
    607    public int getUpdateCount() throws SQLException {
    608        checkOpen();
    609        try {
    610            return statement.getUpdateCount();
    611        } catch (final SQLException e) {
    612            handleException(e);
    613            return 0;
    614        }
    615    }
    616
    617    @Override
    618    public SQLWarning getWarnings() throws SQLException {
    619        checkOpen();
    620        try {
    621            return statement.getWarnings();
    622        } catch (final SQLException e) {
    623            handleException(e);
    624            throw new AssertionError();
    625        }
    626    }
    627
    628    protected void handleException(final SQLException e) throws SQLException {
    629        if (connection != null) {
    630            connection.handleException(e);
    631        } else {
    632            throw e;
    633        }
    634    }
    635
    636    /*
    637     * Note: This method was protected prior to JDBC 4.
    638     */
    639    @Override
    640    public boolean isClosed() throws SQLException {
    641        return closed;
    642    }
    643
    644    protected boolean isClosedInternal() {
    645        return closed;
    646    }
    647
    648    @Override
    649    public boolean isCloseOnCompletion() throws SQLException {
    650        checkOpen();
    651        try {
    652            return Jdbc41Bridge.isCloseOnCompletion(statement);
    653        } catch (final SQLException e) {
    654            handleException(e);
    655            return false;
    656        }
    657    }
    658
    659    @Override
    660    public boolean isPoolable() throws SQLException {
    661        checkOpen();
    662        try {
    663            return statement.isPoolable();
    664        } catch (final SQLException e) {
    665            handleException(e);
    666            return false;
    667        }
    668    }
    669
    670    @Override
    671    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
    672        if (iface.isAssignableFrom(getClass())) {
    673            return true;
    674        } else if (iface.isAssignableFrom(statement.getClass())) {
    675            return true;
    676        } else {
    677            return statement.isWrapperFor(iface);
    678        }
    679    }
    680
    681    /**
    682     *
    683     * @throws SQLException
    684     *             thrown by the delegating statement.
    685     * @since 2.4.0 made public, was protected in 2.3.0.
    686     */
    687    public void passivate() throws SQLException {
    688        if (statement instanceof DelegatingStatement) {
    689            ((DelegatingStatement) statement).passivate();
    690        }
    691    }
    692
    693    protected void setClosedInternal(final boolean closed) {
    694        this.closed = closed;
    695    }
    696
    697    @Override
    698    public void setCursorName(final String name) throws SQLException {
    699        checkOpen();
    700        try {
    701            statement.setCursorName(name);
    702        } catch (final SQLException e) {
    703            handleException(e);
    704        }
    705    }
    706
    707    /**
    708     * Sets my delegate.
    709     *
    710     * @param statement
    711     *            my delegate.
    712     */
    713    public void setDelegate(final Statement statement) {
    714        this.statement = statement;
    715    }
    716
    717    @Override
    718    public void setEscapeProcessing(final boolean enable) throws SQLException {
    719        checkOpen();
    720        try {
    721            statement.setEscapeProcessing(enable);
    722        } catch (final SQLException e) {
    723            handleException(e);
    724        }
    725    }
    726
    727    @Override
    728    public void setFetchDirection(final int direction) throws SQLException {
    729        checkOpen();
    730        try {
    731            statement.setFetchDirection(direction);
    732        } catch (final SQLException e) {
    733            handleException(e);
    734        }
    735    }
    736
    737    @Override
    738    public void setFetchSize(final int rows) throws SQLException {
    739        checkOpen();
    740        try {
    741            statement.setFetchSize(rows);
    742        } catch (final SQLException e) {
    743            handleException(e);
    744        }
    745    }
    746
    747    /**
    748     * @since 2.5.0
    749     */
    750    @Override
    751    public void setLargeMaxRows(final long max) throws SQLException {
    752        checkOpen();
    753        try {
    754            statement.setLargeMaxRows(max);
    755        } catch (final SQLException e) {
    756            handleException(e);
    757        }
    758    }
    759
    760    private void setLastUsedInParent() {
    761        if (connection != null) {
    762            connection.setLastUsed();
    763        }
    764    }
    765
    766    @Override
    767    public void setMaxFieldSize(final int max) throws SQLException {
    768        checkOpen();
    769        try {
    770            statement.setMaxFieldSize(max);
    771        } catch (final SQLException e) {
    772            handleException(e);
    773        }
    774    }
    775
    776    @Override
    777    public void setMaxRows(final int max) throws SQLException {
    778        checkOpen();
    779        try {
    780            statement.setMaxRows(max);
    781        } catch (final SQLException e) {
    782            handleException(e);
    783        }
    784    }
    785
    786    @Override
    787    public void setPoolable(final boolean poolable) throws SQLException {
    788        checkOpen();
    789        try {
    790            statement.setPoolable(poolable);
    791        } catch (final SQLException e) {
    792            handleException(e);
    793        }
    794    }
    795
    796    @Override
    797    public void setQueryTimeout(final int seconds) throws SQLException {
    798        checkOpen();
    799        try {
    800            statement.setQueryTimeout(seconds);
    801        } catch (final SQLException e) {
    802            handleException(e);
    803        }
    804    }
    805
    806    /**
    807     * Returns a String representation of this object.
    808     *
    809     * @return String
    810     */
    811    @Override
    812    public synchronized String toString() {
    813        return statement == null ? "NULL" : statement.toString();
    814    }
    815
    816    @Override
    817    public <T> T unwrap(final Class<T> iface) throws SQLException {
    818        if (iface.isAssignableFrom(getClass())) {
    819            return iface.cast(this);
    820        } else if (iface.isAssignableFrom(statement.getClass())) {
    821            return iface.cast(statement);
    822        } else {
    823            return statement.unwrap(iface);
    824        }
    825    }
    826}