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