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