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