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