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