    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 */
    017
    018package org.apache.commons.dbcp2;
    019
    020import java.sql.Array;
    021import java.sql.Blob;
    022import java.sql.CallableStatement;
    023import java.sql.ClientInfoStatus;
    024import java.sql.Clob;
    025import java.sql.Connection;
    026import java.sql.DatabaseMetaData;
    027import java.sql.NClob;
    028import java.sql.PreparedStatement;
    029import java.sql.ResultSet;
    030import java.sql.SQLClientInfoException;
    031import java.sql.SQLException;
    032import java.sql.SQLWarning;
    033import java.sql.SQLXML;
    034import java.sql.Savepoint;
    035import java.sql.Statement;
    036import java.sql.Struct;
    037import java.util.ArrayList;
    038import java.util.Collections;
    039import java.util.Iterator;
    040import java.util.List;
    041import java.util.Map;
    042import java.util.Properties;
    043import java.util.concurrent.Executor;
    044
    045/**
    046 * A base delegating implementation of {@link Connection}.
    047 * <p>
    048 * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active,
    049 * and call the corresponding method on the "delegate" provided in my constructor.
    050 * </p>
    051 * <p>
    052 * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking
    053 * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of
    054 * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout.
    055 * </p>
    056 *
    057 * @param <C>
    058 *            the Connection type
    059 *
    060 * @since 2.0
    061 */
    062public class DelegatingConnection<C extends Connection> extends AbandonedTrace implements Connection {
    063
    064    private static final Map<String, ClientInfoStatus> EMPTY_FAILED_PROPERTIES = Collections
    065            .<String, ClientInfoStatus>emptyMap();
    066
    067    /** My delegate {@link Connection}. */
    068    private volatile C connection;
    069
    070    private volatile boolean closed;
    071
    072    private boolean cacheState = true;
    073    private Boolean autoCommitCached;
    074    private Boolean readOnlyCached;
    075    private Integer defaultQueryTimeoutSeconds;
    076
    077    /**
    078     * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
    079     *
    080     * @param c
    081     *            the {@link Connection} to delegate all calls to.
    082     */
    083    public DelegatingConnection(final C c) {
    084        super();
    085        connection = c;
    086    }
    087
    088    /**
    089     * Returns a string representation of the metadata associated with the innermost delegate connection.
    090     */
    091    @SuppressWarnings("resource")
    092    @Override
    093    public synchronized String toString() {
    094        String str = null;
    095
    096        final Connection conn = this.getInnermostDelegateInternal();
    097        if (conn != null) {
    098            try {
    099                if (conn.isClosed()) {
    100                    str = "connection is closed";
    101                } else {
    102                    final StringBuffer sb = new StringBuffer();
    103                    sb.append(hashCode());
    104                    final DatabaseMetaData meta = conn.getMetaData();
    105                    if (meta != null) {
    106                        sb.append(", URL=");
    107                        sb.append(meta.getURL());
    108                        sb.append(", ");
    109                        sb.append(meta.getDriverName());
    110                        str = sb.toString();
    111                    }
    112                }
    113            } catch (final SQLException ex) {
    114                // Ignore
    115            }
    116        }
    117        return str != null ? str : super.toString();
    118    }
    119
    120    /**
    121     * Returns my underlying {@link Connection}.
    122     *
    123     * @return my underlying {@link Connection}.
    124     */
    125    public C getDelegate() {
    126        return getDelegateInternal();
    127    }
    128
    129    protected final C getDelegateInternal() {
    130        return connection;
    131    }
    132
    133    /**
    134     * Compares innermost delegate to the given connection.
    135     *
    136     * @param c
    137     *            connection to compare innermost delegate with
    138     * @return true if innermost delegate equals <code>c</code>
    139     */
    140    @SuppressWarnings("resource")
    141    public boolean innermostDelegateEquals(final Connection c) {
    142        final Connection innerCon = getInnermostDelegateInternal();
    143        if (innerCon == null) {
    144            return c == null;
    145        }
    146        return innerCon.equals(c);
    147    }
    148
    149    /**
    150     * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively
    151     * invokes this method on my delegate.
    152     * <p>
    153     * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when
    154     * no non-{@code DelegatingConnection} delegate can be found by traversing this chain.
    155     * </p>
    156     * <p>
    157     * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain
    158     * a "genuine" {@link Connection}.
    159     * </p>
    160     *
    161     * @return innermost delegate.
    162     */
    163    public Connection getInnermostDelegate() {
    164        return getInnermostDelegateInternal();
    165    }
    166
    167    /**
    168     * Although this method is public, it is part of the internal API and should not be used by clients. The signature
    169     * of this method may change at any time including in ways that break backwards compatibility.
    170     *
    171     * @return innermost delegate.
    172     */
    173    @SuppressWarnings("resource")
    174    public final Connection getInnermostDelegateInternal() {
    175        Connection conn = connection;
    176        while (conn != null && conn instanceof DelegatingConnection) {
    177            conn = ((DelegatingConnection<?>) conn).getDelegateInternal();
    178            if (this == conn) {
    179                return null;
    180            }
    181        }
    182        return conn;
    183    }
    184
    185    /**
    186     * Sets my delegate.
    187     *
    188     * @param connection
    189     *            my delegate.
    190     */
    191    public void setDelegate(final C connection) {
    192        this.connection = connection;
    193    }
    194
    195    /**
    196     * Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that
    197     * override this method must:
    198     * <ol>
    199     * <li>Call passivate()</li>
    200     * <li>Call close (or the equivalent appropriate action) on the wrapped connection</li>
    201     * <li>Set _closed to <code>false</code></li>
    202     * </ol>
    203     */
    204    @Override
    205    public void close() throws SQLException {
    206        if (!closed) {
    207            closeInternal();
    208        }
    209    }
    210
    211    protected boolean isClosedInternal() {
    212        return closed;
    213    }
    214
    215    protected void setClosedInternal(final boolean closed) {
    216        this.closed = closed;
    217    }
    218
    219    protected final void closeInternal() throws SQLException {
    220        try {
    221            passivate();
    222        } finally {
    223            if (connection != null) {
    224                boolean connectionIsClosed;
    225                try {
    226                    connectionIsClosed = connection.isClosed();
    227                } catch (final SQLException e) {
    228                    // not sure what the state is, so assume the connection is open.
    229                    connectionIsClosed = false;
    230                }
    231                try {
    232                    // DBCP-512: Avoid exceptions when closing a connection in mutli-threaded use case.
    233                    // Avoid closing again, which should be a no-op, but some drivers like H2 throw an exception when
    234                    // closing from multiple threads.
    235                    if (!connectionIsClosed) {
    236                        connection.close();
    237                    }
    238                } finally {
    239                    closed = true;
    240                }
    241            } else {
    242                closed = true;
    243            }
    244        }
    245    }
    246
    247    protected void handleException(final SQLException e) throws SQLException {
    248        throw e;
    249    }
    250
    251    /**
    252     * Handles the given {@code SQLException}.
    253     *
    254     * @param <T> The throwable type.
    255     * @param e   The SQLException
    256     * @return the given {@code SQLException}
    257     * @since 2.7.0
    258     */
    259    protected <T extends Throwable> T handleExceptionNoThrow(final T e) {
    260        return e;
    261    }
    262
    263    private void initializeStatement(final DelegatingStatement ds) throws SQLException {
    264        if (defaultQueryTimeoutSeconds != null && defaultQueryTimeoutSeconds.intValue() != ds.getQueryTimeout()) {
    265            ds.setQueryTimeout(defaultQueryTimeoutSeconds.intValue());
    266        }
    267    }
    268
    269    @Override
    270    public Statement createStatement() throws SQLException {
    271        checkOpen();
    272        try {
    273            final DelegatingStatement ds = new DelegatingStatement(this, connection.createStatement());
    274            initializeStatement(ds);
    275            return ds;
    276        } catch (final SQLException e) {
    277            handleException(e);
    278            return null;
    279        }
    280    }
    281
    282    @Override
    283    public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
    284        checkOpen();
    285        try {
    286            final DelegatingStatement ds = new DelegatingStatement(this,
    287                    connection.createStatement(resultSetType, resultSetConcurrency));
    288            initializeStatement(ds);
    289            return ds;
    290        } catch (final SQLException e) {
    291            handleException(e);
    292            return null;
    293        }
    294    }
    295
    296    @Override
    297    public PreparedStatement prepareStatement(final String sql) throws SQLException {
    298        checkOpen();
    299        try {
    300            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
    301                    connection.prepareStatement(sql));
    302            initializeStatement(dps);
    303            return dps;
    304        } catch (final SQLException e) {
    305            handleException(e);
    306            return null;
    307        }
    308    }
    309
    310    @Override
    311    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
    312            throws SQLException {
    313        checkOpen();
    314        try {
    315            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
    316                    connection.prepareStatement(sql, resultSetType, resultSetConcurrency));
    317            initializeStatement(dps);
    318            return dps;
    319        } catch (final SQLException e) {
    320            handleException(e);
    321            return null;
    322        }
    323    }
    324
    325    @Override
    326    public CallableStatement prepareCall(final String sql) throws SQLException {
    327        checkOpen();
    328        try {
    329            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this, connection.prepareCall(sql));
    330            initializeStatement(dcs);
    331            return dcs;
    332        } catch (final SQLException e) {
    333            handleException(e);
    334            return null;
    335        }
    336    }
    337
    338    @Override
    339    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
    340            throws SQLException {
    341        checkOpen();
    342        try {
    343            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this,
    344                    connection.prepareCall(sql, resultSetType, resultSetConcurrency));
    345            initializeStatement(dcs);
    346            return dcs;
    347        } catch (final SQLException e) {
    348            handleException(e);
    349            return null;
    350        }
    351    }
    352
    353    @Override
    354    public void clearWarnings() throws SQLException {
    355        checkOpen();
    356        try {
    357            connection.clearWarnings();
    358        } catch (final SQLException e) {
    359            handleException(e);
    360        }
    361    }
    362
    363    @Override
    364    public void commit() throws SQLException {
    365        checkOpen();
    366        try {
    367            connection.commit();
    368        } catch (final SQLException e) {
    369            handleException(e);
    370        }
    371    }
    372
    373    /**
    374     * Returns the state caching flag.
    375     *
    376     * @return the state caching flag
    377     */
    378    public boolean getCacheState() {
    379        return cacheState;
    380    }
    381
    382    @Override
    383    public boolean getAutoCommit() throws SQLException {
    384        checkOpen();
    385        if (cacheState && autoCommitCached != null) {
    386            return autoCommitCached.booleanValue();
    387        }
    388        try {
    389            autoCommitCached = Boolean.valueOf(connection.getAutoCommit());
    390            return autoCommitCached.booleanValue();
    391        } catch (final SQLException e) {
    392            handleException(e);
    393            return false;
    394        }
    395    }
    396
    397    @Override
    398    public String getCatalog() throws SQLException {
    399        checkOpen();
    400        try {
    401            return connection.getCatalog();
    402        } catch (final SQLException e) {
    403            handleException(e);
    404            return null;
    405        }
    406    }
    407
    408    @Override
    409    public DatabaseMetaData getMetaData() throws SQLException {
    410        checkOpen();
    411        try {
    412            return new DelegatingDatabaseMetaData(this, connection.getMetaData());
    413        } catch (final SQLException e) {
    414            handleException(e);
    415            return null;
    416        }
    417    }
    418
    419    @Override
    420    public int getTransactionIsolation() throws SQLException {
    421        checkOpen();
    422        try {
    423            return connection.getTransactionIsolation();
    424        } catch (final SQLException e) {
    425            handleException(e);
    426            return -1;
    427        }
    428    }
    429
    430    @Override
    431    public Map<String, Class<?>> getTypeMap() throws SQLException {
    432        checkOpen();
    433        try {
    434            return connection.getTypeMap();
    435        } catch (final SQLException e) {
    436            handleException(e);
    437            return null;
    438        }
    439    }
    440
    441    @Override
    442    public SQLWarning getWarnings() throws SQLException {
    443        checkOpen();
    444        try {
    445            return connection.getWarnings();
    446        } catch (final SQLException e) {
    447            handleException(e);
    448            return null;
    449        }
    450    }
    451
    452    @Override
    453    public boolean isReadOnly() throws SQLException {
    454        checkOpen();
    455        if (cacheState && readOnlyCached != null) {
    456            return readOnlyCached.booleanValue();
    457        }
    458        try {
    459            readOnlyCached = Boolean.valueOf(connection.isReadOnly());
    460            return readOnlyCached.booleanValue();
    461        } catch (final SQLException e) {
    462            handleException(e);
    463            return false;
    464        }
    465    }
    466
    467    @Override
    468    public String nativeSQL(final String sql) throws SQLException {
    469        checkOpen();
    470        try {
    471            return connection.nativeSQL(sql);
    472        } catch (final SQLException e) {
    473            handleException(e);
    474            return null;
    475        }
    476    }
    477
    478    @Override
    479    public void rollback() throws SQLException {
    480        checkOpen();
    481        try {
    482            connection.rollback();
    483        } catch (final SQLException e) {
    484            handleException(e);
    485        }
    486    }
    487
    488    /**
    489     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
    490     * <code>null</code> means that the driver default will be used.
    491     *
    492     * @return query timeout limit in seconds; zero means there is no limit.
    493     */
    494    public Integer getDefaultQueryTimeout() {
    495        return defaultQueryTimeoutSeconds;
    496    }
    497
    498    /**
    499     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
    500     * <code>null</code> means that the driver default will be used.
    501     *
    502     * @param defaultQueryTimeoutSeconds
    503     *            the new query timeout limit in seconds; zero means there is no limit
    504     */
    505    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
    506        this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
    507    }
    508
    509    /**
    510     * Sets the state caching flag.
    511     *
    512     * @param cacheState
    513     *            The new value for the state caching flag
    514     */
    515    public void setCacheState(final boolean cacheState) {
    516        this.cacheState = cacheState;
    517    }
    518
    519    /**
    520     * Can be used to clear cached state when it is known that the underlying connection may have been accessed
    521     * directly.
    522     */
    523    public void clearCachedState() {
    524        autoCommitCached = null;
    525        readOnlyCached = null;
    526        if (connection instanceof DelegatingConnection) {
    527            ((DelegatingConnection<?>) connection).clearCachedState();
    528        }
    529    }
    530
    531    @Override
    532    public void setAutoCommit(final boolean autoCommit) throws SQLException {
    533        checkOpen();
    534        try {
    535            connection.setAutoCommit(autoCommit);
    536            if (cacheState) {
    537                autoCommitCached = Boolean.valueOf(connection.getAutoCommit());
    538            }
    539        } catch (final SQLException e) {
    540            autoCommitCached = null;
    541            handleException(e);
    542        }
    543    }
    544
    545    @Override
    546    public void setCatalog(final String catalog) throws SQLException {
    547        checkOpen();
    548        try {
    549            connection.setCatalog(catalog);
    550        } catch (final SQLException e) {
    551            handleException(e);
    552        }
    553    }
    554
    555    @Override
    556    public void setReadOnly(final boolean readOnly) throws SQLException {
    557        checkOpen();
    558        try {
    559            connection.setReadOnly(readOnly);
    560            if (cacheState) {
    561                readOnlyCached = Boolean.valueOf(connection.isReadOnly());
    562            }
    563        } catch (final SQLException e) {
    564            readOnlyCached = null;
    565            handleException(e);
    566        }
    567    }
    568
    569    @Override
    570    public void setTransactionIsolation(final int level) throws SQLException {
    571        checkOpen();
    572        try {
    573            connection.setTransactionIsolation(level);
    574        } catch (final SQLException e) {
    575            handleException(e);
    576        }
    577    }
    578
    579    @Override
    580    public void setTypeMap(final Map<String, Class<?>> map) throws SQLException {
    581        checkOpen();
    582        try {
    583            connection.setTypeMap(map);
    584        } catch (final SQLException e) {
    585            handleException(e);
    586        }
    587    }
    588
    589    @Override
    590    public boolean isClosed() throws SQLException {
    591        return closed || connection == null || connection.isClosed();
    592    }
    593
    594    protected void checkOpen() throws SQLException {
    595        if (closed) {
    596            if (null != connection) {
    597                String label = "";
    598                try {
    599                    label = connection.toString();
    600                } catch (final Exception ex) {
    601                    // ignore, leave label empty
    602                }
    603                throw new SQLException("Connection " + label + " is closed.");
    604            }
    605            throw new SQLException("Connection is null.");
    606        }
    607    }
    608
    609    protected void activate() {
    610        closed = false;
    611        setLastUsed();
    612        if (connection instanceof DelegatingConnection) {
    613            ((DelegatingConnection<?>) connection).activate();
    614        }
    615    }
    616
    617    protected void passivate() throws SQLException {
    618        // The JDBC specification requires that a Connection close any open
    619        // Statement's when it is closed.
    620        // DBCP-288. Not all the traced objects will be statements
    621        final List<AbandonedTrace> traces = getTrace();
    622        if (traces != null && !traces.isEmpty()) {
    623            final List<Exception> thrownList = new ArrayList<>();
    624            final Iterator<AbandonedTrace> traceIter = traces.iterator();
    625            while (traceIter.hasNext()) {
    626                final Object trace = traceIter.next();
    627                if (trace instanceof Statement) {
    628                    try {
    629                        ((Statement) trace).close();
    630                    } catch (Exception e) {
    631                        thrownList.add(e);
    632                    }
    633                } else if (trace instanceof ResultSet) {
    634                    // DBCP-265: Need to close the result sets that are
    635                    // generated via DatabaseMetaData
    636                    try {
    637                        ((ResultSet) trace).close();
    638                    } catch (Exception e) {
    639                        thrownList.add(e);
    640                    }
    641                }
    642            }
    643            clearTrace();
    644            if (!thrownList.isEmpty()) {
    645                throw new SQLExceptionList(thrownList);
    646            }
    647        }
    648        setLastUsed(0);
    649    }
    650
    651    @Override
    652    public int getHoldability() throws SQLException {
    653        checkOpen();
    654        try {
    655            return connection.getHoldability();
    656        } catch (final SQLException e) {
    657            handleException(e);
    658            return 0;
    659        }
    660    }
    661
    662    @Override
    663    public void setHoldability(final int holdability) throws SQLException {
    664        checkOpen();
    665        try {
    666            connection.setHoldability(holdability);
    667        } catch (final SQLException e) {
    668            handleException(e);
    669        }
    670    }
    671
    672    @Override
    673    public Savepoint setSavepoint() throws SQLException {
    674        checkOpen();
    675        try {
    676            return connection.setSavepoint();
    677        } catch (final SQLException e) {
    678            handleException(e);
    679            return null;
    680        }
    681    }
    682
    683    @Override
    684    public Savepoint setSavepoint(final String name) throws SQLException {
    685        checkOpen();
    686        try {
    687            return connection.setSavepoint(name);
    688        } catch (final SQLException e) {
    689            handleException(e);
    690            return null;
    691        }
    692    }
    693
    694    @Override
    695    public void rollback(final Savepoint savepoint) throws SQLException {
    696        checkOpen();
    697        try {
    698            connection.rollback(savepoint);
    699        } catch (final SQLException e) {
    700            handleException(e);
    701        }
    702    }
    703
    704    @Override
    705    public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
    706        checkOpen();
    707        try {
    708            connection.releaseSavepoint(savepoint);
    709        } catch (final SQLException e) {
    710            handleException(e);
    711        }
    712    }
    713
    714    @Override
    715    public Statement createStatement(final int resultSetType, final int resultSetConcurrency,
    716            final int resultSetHoldability) throws SQLException {
    717        checkOpen();
    718        try {
    719            final DelegatingStatement ds = new DelegatingStatement(this,
    720                    connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
    721            initializeStatement(ds);
    722            return ds;
    723        } catch (final SQLException e) {
    724            handleException(e);
    725            return null;
    726        }
    727    }
    728
    729    @Override
    730    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
    731            final int resultSetHoldability) throws SQLException {
    732        checkOpen();
    733        try {
    734            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
    735                    connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
    736            initializeStatement(dps);
    737            return dps;
    738        } catch (final SQLException e) {
    739            handleException(e);
    740            return null;
    741        }
    742    }
    743
    744    @Override
    745    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
    746            final int resultSetHoldability) throws SQLException {
    747        checkOpen();
    748        try {
    749            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this,
    750                    connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
    751            initializeStatement(dcs);
    752            return dcs;
    753        } catch (final SQLException e) {
    754            handleException(e);
    755            return null;
    756        }
    757    }
    758
    759    @Override
    760    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
    761        checkOpen();
    762        try {
    763            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
    764                    connection.prepareStatement(sql, autoGeneratedKeys));
    765            initializeStatement(dps);
    766            return dps;
    767        } catch (final SQLException e) {
    768            handleException(e);
    769            return null;
    770        }
    771    }
    772
    773    @Override
    774    public PreparedStatement prepareStatement(final String sql, final int columnIndexes[]) throws SQLException {
    775        checkOpen();
    776        try {
    777            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
    778                    connection.prepareStatement(sql, columnIndexes));
    779            initializeStatement(dps);
    780            return dps;
    781        } catch (final SQLException e) {
    782            handleException(e);
    783            return null;
    784        }
    785    }
    786
    787    @Override
    788    public PreparedStatement prepareStatement(final String sql, final String columnNames[]) throws SQLException {
    789        checkOpen();
    790        try {
    791            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
    792                    connection.prepareStatement(sql, columnNames));
    793            initializeStatement(dps);
    794            return dps;
    795        } catch (final SQLException e) {
    796            handleException(e);
    797            return null;
    798        }
    799    }
    800
    801    @Override
    802    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
    803        if (iface.isAssignableFrom(getClass())) {
    804            return true;
    805        } else if (iface.isAssignableFrom(connection.getClass())) {
    806            return true;
    807        } else {
    808            return connection.isWrapperFor(iface);
    809        }
    810    }
    811
    812    @Override
    813    public <T> T unwrap(final Class<T> iface) throws SQLException {
    814        if (iface.isAssignableFrom(getClass())) {
    815            return iface.cast(this);
    816        } else if (iface.isAssignableFrom(connection.getClass())) {
    817            return iface.cast(connection);
    818        } else {
    819            return connection.unwrap(iface);
    820        }
    821    }
    822
    823    @Override
    824    public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
    825        checkOpen();
    826        try {
    827            return connection.createArrayOf(typeName, elements);
    828        } catch (final SQLException e) {
    829            handleException(e);
    830            return null;
    831        }
    832    }
    833
    834    @Override
    835    public Blob createBlob() throws SQLException {
    836        checkOpen();
    837        try {
    838            return connection.createBlob();
    839        } catch (final SQLException e) {
    840            handleException(e);
    841            return null;
    842        }
    843    }
    844
    845    @Override
    846    public Clob createClob() throws SQLException {
    847        checkOpen();
    848        try {
    849            return connection.createClob();
    850        } catch (final SQLException e) {
    851            handleException(e);
    852            return null;
    853        }
    854    }
    855
    856    @Override
    857    public NClob createNClob() throws SQLException {
    858        checkOpen();
    859        try {
    860            return connection.createNClob();
    861        } catch (final SQLException e) {
    862            handleException(e);
    863            return null;
    864        }
    865    }
    866
    867    @Override
    868    public SQLXML createSQLXML() throws SQLException {
    869        checkOpen();
    870        try {
    871            return connection.createSQLXML();
    872        } catch (final SQLException e) {
    873            handleException(e);
    874            return null;
    875        }
    876    }
    877
    878    @Override
    879    public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
    880        checkOpen();
    881        try {
    882            return connection.createStruct(typeName, attributes);
    883        } catch (final SQLException e) {
    884            handleException(e);
    885            return null;
    886        }
    887    }
    888
    889    @Override
    890    public boolean isValid(final int timeoutSeconds) throws SQLException {
    891        if (isClosed()) {
    892            return false;
    893        }
    894        try {
    895            return connection.isValid(timeoutSeconds);
    896        } catch (final SQLException e) {
    897            handleException(e);
    898            return false;
    899        }
    900    }
    901
    902    @Override
    903    public void setClientInfo(final String name, final String value) throws SQLClientInfoException {
    904        try {
    905            checkOpen();
    906            connection.setClientInfo(name, value);
    907        } catch (final SQLClientInfoException e) {
    908            throw e;
    909        } catch (final SQLException e) {
    910            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
    911        }
    912    }
    913
    914    @Override
    915    public void setClientInfo(final Properties properties) throws SQLClientInfoException {
    916        try {
    917            checkOpen();
    918            connection.setClientInfo(properties);
    919        } catch (final SQLClientInfoException e) {
    920            throw e;
    921        } catch (final SQLException e) {
    922            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
    923        }
    924    }
    925
    926    @Override
    927    public Properties getClientInfo() throws SQLException {
    928        checkOpen();
    929        try {
    930            return connection.getClientInfo();
    931        } catch (final SQLException e) {
    932            handleException(e);
    933            return null;
    934        }
    935    }
    936
    937    @Override
    938    public String getClientInfo(final String name) throws SQLException {
    939        checkOpen();
    940        try {
    941            return connection.getClientInfo(name);
    942        } catch (final SQLException e) {
    943            handleException(e);
    944            return null;
    945        }
    946    }
    947
    948    @Override
    949    public void setSchema(final String schema) throws SQLException {
    950        checkOpen();
    951        try {
    952            Jdbc41Bridge.setSchema(connection, schema);
    953        } catch (final SQLException e) {
    954            handleException(e);
    955        }
    956    }
    957
    958    @Override
    959    public String getSchema() throws SQLException {
    960        checkOpen();
    961        try {
    962            return Jdbc41Bridge.getSchema(connection);
    963        } catch (final SQLException e) {
    964            handleException(e);
    965            return null;
    966        }
    967    }
    968
    969    @Override
    970    public void abort(final Executor executor) throws SQLException {
    971        checkOpen();
    972        try {
    973            Jdbc41Bridge.abort(connection, executor);
    974        } catch (final SQLException e) {
    975            handleException(e);
    976        }
    977    }
    978
    979    @Override
    980    public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException {
    981        checkOpen();
    982        try {
    983            Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds);
    984        } catch (final SQLException e) {
    985            handleException(e);
    986        }
    987    }
    988
    989    @Override
    990    public int getNetworkTimeout() throws SQLException {
    991        checkOpen();
    992        try {
    993            return Jdbc41Bridge.getNetworkTimeout(connection);
    994        } catch (final SQLException e) {
    995            handleException(e);
    996            return 0;
    997        }
    998    }
    999}