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