    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;
    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> thrownList = 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> traceList = getTrace();
    141            if (traceList != null) {
    142                traceList.forEach(trace -> trace.close(e -> {
    143                    if (connection != null) {
    144                        // Does not rethrow e.
    145                        connection.handleExceptionNoThrow(e);
    146                    }
    147                    thrownList.add(e);
    148                }));
    149                clearTrace();
    150            }
    151            Utils.close(statement, e -> {
    152                if (connection != null) {
    153                    // Does not rethrow e.
    154                    connection.handleExceptionNoThrow(e);
    155                }
    156                thrownList.add(e);
    157            });
    158        } finally {
    159            closed = true;
    160            statement = null;
    161            if (!thrownList.isEmpty()) {
    162                throw new SQLExceptionList(thrownList);
    163            }
    164        }
    165    }
    166
    167    @Override
    168    public void closeOnCompletion() throws SQLException {
    169        checkOpen();
    170        try {
    171            Jdbc41Bridge.closeOnCompletion(statement);
    172        } catch (final SQLException e) {
    173            handleException(e);
    174        }
    175    }
    176
    177    @Override
    178    public boolean execute(final String sql) throws SQLException {
    179        checkOpen();
    180        setLastUsedInParent();
    181        try {
    182            return statement.execute(sql);
    183        } catch (final SQLException e) {
    184            handleException(e);
    185            return false;
    186        }
    187    }
    188
    189    @Override
    190    public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
    191        checkOpen();
    192        setLastUsedInParent();
    193        try {
    194            return statement.execute(sql, autoGeneratedKeys);
    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[] columnIndexes) throws SQLException {
    203        checkOpen();
    204        setLastUsedInParent();
    205        try {
    206            return statement.execute(sql, columnIndexes);
    207        } catch (final SQLException e) {
    208            handleException(e);
    209            return false;
    210        }
    211    }
    212
    213    @Override
    214    public boolean execute(final String sql, final String[] columnNames) throws SQLException {
    215        checkOpen();
    216        setLastUsedInParent();
    217        try {
    218            return statement.execute(sql, columnNames);
    219        } catch (final SQLException e) {
    220            handleException(e);
    221            return false;
    222        }
    223    }
    224
    225    @Override
    226    public int[] executeBatch() throws SQLException {
    227        checkOpen();
    228        setLastUsedInParent();
    229        try {
    230            return statement.executeBatch();
    231        } catch (final SQLException e) {
    232            handleException(e);
    233            throw new AssertionError();
    234        }
    235    }
    236
    237    /**
    238     * @since 2.5.0
    239     */
    240    @Override
    241    public long[] executeLargeBatch() throws SQLException {
    242        checkOpen();
    243        setLastUsedInParent();
    244        try {
    245            return statement.executeLargeBatch();
    246        } catch (final SQLException e) {
    247            handleException(e);
    248            return null;
    249        }
    250    }
    251
    252    /**
    253     * @since 2.5.0
    254     */
    255    @Override
    256    public long executeLargeUpdate(final String sql) throws SQLException {
    257        checkOpen();
    258        setLastUsedInParent();
    259        try {
    260            return statement.executeLargeUpdate(sql);
    261        } catch (final SQLException e) {
    262            handleException(e);
    263            return 0;
    264        }
    265    }
    266
    267    /**
    268     * @since 2.5.0
    269     */
    270    @Override
    271    public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
    272        checkOpen();
    273        setLastUsedInParent();
    274        try {
    275            return statement.executeLargeUpdate(sql, autoGeneratedKeys);
    276        } catch (final SQLException e) {
    277            handleException(e);
    278            return 0;
    279        }
    280    }
    281
    282    /**
    283     * @since 2.5.0
    284     */
    285    @Override
    286    public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
    287        checkOpen();
    288        setLastUsedInParent();
    289        try {
    290            return statement.executeLargeUpdate(sql, columnIndexes);
    291        } catch (final SQLException e) {
    292            handleException(e);
    293            return 0;
    294        }
    295    }
    296
    297    /**
    298     * @since 2.5.0
    299     */
    300    @Override
    301    public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException {
    302        checkOpen();
    303        setLastUsedInParent();
    304        try {
    305            return statement.executeLargeUpdate(sql, columnNames);
    306        } catch (final SQLException e) {
    307            handleException(e);
    308            return 0;
    309        }
    310    }
    311
    312    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
    313    @Override
    314    public ResultSet executeQuery(final String sql) throws SQLException {
    315        checkOpen();
    316        setLastUsedInParent();
    317        try {
    318            return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql));
    319        } catch (final SQLException e) {
    320            handleException(e);
    321            throw new AssertionError();
    322        }
    323    }
    324
    325    @Override
    326    public int executeUpdate(final String sql) throws SQLException {
    327        checkOpen();
    328        setLastUsedInParent();
    329        try {
    330            return statement.executeUpdate(sql);
    331        } catch (final SQLException e) {
    332            handleException(e);
    333            return 0;
    334        }
    335    }
    336
    337    @Override
    338    public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
    339        checkOpen();
    340        setLastUsedInParent();
    341        try {
    342            return statement.executeUpdate(sql, autoGeneratedKeys);
    343        } catch (final SQLException e) {
    344            handleException(e);
    345            return 0;
    346        }
    347    }
    348
    349    @Override
    350    public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
    351        checkOpen();
    352        setLastUsedInParent();
    353        try {
    354            return statement.executeUpdate(sql, columnIndexes);
    355        } catch (final SQLException e) {
    356            handleException(e);
    357            return 0;
    358        }
    359    }
    360
    361    @Override
    362    public int executeUpdate(final String sql, final String[] columnNames) throws SQLException {
    363        checkOpen();
    364        setLastUsedInParent();
    365        try {
    366            return statement.executeUpdate(sql, columnNames);
    367        } catch (final SQLException e) {
    368            handleException(e);
    369            return 0;
    370        }
    371    }
    372
    373    @Override
    374    protected void finalize() throws Throwable {
    375        // This is required because of statement pooling. The poolable
    376        // statements will always be strongly held by the statement pool. If the
    377        // delegating statements that wrap the poolable statement are not
    378        // strongly held they will be garbage collected but at that point the
    379        // poolable statements need to be returned to the pool else there will
    380        // be a leak of statements from the pool. Closing this statement will
    381        // close all the wrapped statements and return any poolable statements
    382        // to the pool.
    383        close();
    384        super.finalize();
    385    }
    386
    387    @Override
    388    public Connection getConnection() throws SQLException {
    389        checkOpen();
    390        return getConnectionInternal(); // return the delegating connection that created this
    391    }
    392
    393    protected DelegatingConnection<?> getConnectionInternal() {
    394        return connection;
    395    }
    396
    397    /**
    398     * Returns my underlying {@link Statement}.
    399     *
    400     * @return my underlying {@link Statement}.
    401     * @see #getInnermostDelegate
    402     */
    403    public Statement getDelegate() {
    404        return statement;
    405    }
    406
    407    @Override
    408    public int getFetchDirection() throws SQLException {
    409        checkOpen();
    410        try {
    411            return statement.getFetchDirection();
    412        } catch (final SQLException e) {
    413            handleException(e);
    414            return 0;
    415        }
    416    }
    417
    418    @Override
    419    public int getFetchSize() throws SQLException {
    420        checkOpen();
    421        try {
    422            return statement.getFetchSize();
    423        } catch (final SQLException e) {
    424            handleException(e);
    425            return 0;
    426        }
    427    }
    428
    429    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
    430    @Override
    431    public ResultSet getGeneratedKeys() throws SQLException {
    432        checkOpen();
    433        try {
    434            return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys());
    435        } catch (final SQLException e) {
    436            handleException(e);
    437            throw new AssertionError();
    438        }
    439    }
    440
    441    /**
    442     * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively
    443     * invokes this method on my delegate.
    444     * <p>
    445     * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when
    446     * no non-{@code DelegatingStatement} delegate can be found by traversing this chain.
    447     * </p>
    448     * <p>
    449     * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain
    450     * a "genuine" {@link Statement}.
    451     * </p>
    452     *
    453     * @return The innermost delegate.
    454     *
    455     * @see #getDelegate
    456     */
    457    @SuppressWarnings("resource")
    458    public Statement getInnermostDelegate() {
    459        Statement s = statement;
    460        while (s instanceof DelegatingStatement) {
    461            s = ((DelegatingStatement) s).getDelegate();
    462            if (this == s) {
    463                return null;
    464            }
    465        }
    466        return s;
    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 statement == null ? "NULL" : statement.toString();
    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}