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