001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbcp2;
018
019import java.sql.Array;
020import java.sql.Blob;
021import java.sql.CallableStatement;
022import java.sql.ClientInfoStatus;
023import java.sql.Clob;
024import java.sql.Connection;
025import java.sql.DatabaseMetaData;
026import java.sql.NClob;
027import java.sql.PreparedStatement;
028import java.sql.SQLClientInfoException;
029import java.sql.SQLException;
030import java.sql.SQLWarning;
031import java.sql.SQLXML;
032import java.sql.Savepoint;
033import java.sql.Statement;
034import java.sql.Struct;
035import java.time.Duration;
036import java.time.Instant;
037import java.util.ArrayList;
038import java.util.Collections;
039import java.util.List;
040import java.util.Map;
041import java.util.Properties;
042import java.util.concurrent.Executor;
043
044import org.apache.commons.dbcp2.managed.ManagedConnection;
045
046/**
047 * A base delegating implementation of {@link Connection}.
048 * <p>
049 * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active,
050 * and call the corresponding method on the "delegate" provided in my constructor.
051 * </p>
052 * <p>
053 * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking
054 * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of
055 * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout.
056 * </p>
057 *
058 * @param <C>
059 *            the Connection type
060 *
061 * @since 2.0
062 */
063public class DelegatingConnection<C extends Connection> extends AbandonedTrace implements Connection {
064
065    private static final Map<String, ClientInfoStatus> EMPTY_FAILED_PROPERTIES = Collections
066            .<String, ClientInfoStatus>emptyMap();
067
068    /** My delegate {@link Connection}. */
069    private volatile C connection;
070
071    private volatile boolean closed;
072
073    private boolean cacheState = true;
074    private Boolean cachedAutoCommit;
075    private Boolean cachedReadOnly;
076    private String cachedCatalog;
077    private String cachedSchema;
078    private Duration defaultQueryTimeoutDuration;
079
080    /**
081     * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
082     *
083     * @param connection the {@link Connection} to delegate all calls to, may be null (see {@link ManagedConnection}).
084     */
085    public DelegatingConnection(final C connection) {
086        this.connection = connection;
087    }
088
089    @Override
090    public void abort(final Executor executor) throws SQLException {
091        try {
092            Jdbc41Bridge.abort(connection, executor);
093        } catch (final SQLException e) {
094            handleException(e);
095        }
096    }
097
098    /**
099     * 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}