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