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