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