View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbcp2;
18  
19  import java.sql.Array;
20  import java.sql.Blob;
21  import java.sql.CallableStatement;
22  import java.sql.ClientInfoStatus;
23  import java.sql.Clob;
24  import java.sql.Connection;
25  import java.sql.DatabaseMetaData;
26  import java.sql.NClob;
27  import java.sql.PreparedStatement;
28  import java.sql.SQLClientInfoException;
29  import java.sql.SQLException;
30  import java.sql.SQLWarning;
31  import java.sql.SQLXML;
32  import java.sql.Savepoint;
33  import java.sql.Statement;
34  import java.sql.Struct;
35  import java.time.Duration;
36  import java.time.Instant;
37  import java.util.ArrayList;
38  import java.util.Collections;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Properties;
42  import java.util.concurrent.Executor;
43  
44  import org.apache.commons.dbcp2.managed.ManagedConnection;
45  
46  /**
47   * A base delegating implementation of {@link Connection}.
48   * <p>
49   * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active,
50   * and call the corresponding method on the "delegate" provided in my constructor.
51   * </p>
52   * <p>
53   * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking
54   * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of
55   * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout.
56   * </p>
57   *
58   * @param <C>
59   *            the Connection type
60   *
61   * @since 2.0
62   */
63  public class DelegatingConnection<C extends Connection> extends AbandonedTrace implements Connection {
64  
65      private static final Map<String, ClientInfoStatus> EMPTY_FAILED_PROPERTIES = Collections
66              .<String, ClientInfoStatus>emptyMap();
67  
68      /** My delegate {@link Connection}. */
69      private volatile C connection;
70  
71      private volatile boolean closed;
72  
73      private boolean cacheState = true;
74      private Boolean cachedAutoCommit;
75      private Boolean cachedReadOnly;
76      private String cachedCatalog;
77      private String cachedSchema;
78      private Duration defaultQueryTimeoutDuration;
79  
80      /**
81       * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
82       *
83       * @param connection the {@link Connection} to delegate all calls to, may be null (see {@link ManagedConnection}).
84       */
85      public DelegatingConnection(final C connection) {
86          this.connection = connection;
87      }
88  
89      @Override
90      public void abort(final Executor executor) throws SQLException {
91          try {
92              Jdbc41Bridge.abort(connection, executor);
93          } catch (final SQLException e) {
94              handleException(e);
95          }
96      }
97  
98      protected void activate() {
99          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 }