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