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