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