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