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}