1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.tomcat.dbcp.dbcp2;
19
20 import java.sql.Array;
21 import java.sql.Blob;
22 import java.sql.CallableStatement;
23 import java.sql.ClientInfoStatus;
24 import java.sql.Clob;
25 import java.sql.Connection;
26 import java.sql.DatabaseMetaData;
27 import java.sql.NClob;
28 import java.sql.PreparedStatement;
29 import java.sql.ResultSet;
30 import java.sql.SQLClientInfoException;
31 import java.sql.SQLException;
32 import java.sql.SQLWarning;
33 import java.sql.SQLXML;
34 import java.sql.Savepoint;
35 import java.sql.Statement;
36 import java.sql.Struct;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Properties;
43 import java.util.concurrent.Executor;
44
45 /**
46  * A base delegating implementation of {@link Connection}.
47  * <p>
48  * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active,
49  * and call the corresponding method on the "delegate" provided in my constructor.
50  * </p>
51  * <p>
52  * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking
53  * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of
54  * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout.
55  * </p>
56  *
57  * @param <C>
58  *            the Connection type
59  *
60  * @since 2.0
61  */

62 public class DelegatingConnection<C extends Connection> extends AbandonedTrace implements Connection {
63
64     private static final Map<String, ClientInfoStatus> EMPTY_FAILED_PROPERTIES = Collections
65             .<String, ClientInfoStatus>emptyMap();
66
67     /** My delegate {@link Connection}. */
68     private volatile C connection;
69
70     private volatile boolean closed;
71
72     private boolean cacheState = true;
73     private Boolean autoCommitCached;
74     private Boolean readOnlyCached;
75     private Integer defaultQueryTimeoutSeconds;
76
77     /**
78      * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
79      *
80      * @param c
81      *            the {@link Connection} to delegate all calls to.
82      */

83     public DelegatingConnection(final C c) {
84         super();
85         connection = c;
86     }
87
88     /**
89      * Returns a string representation of the metadata associated with the innermost delegate connection.
90      */

91     @SuppressWarnings("resource")
92     @Override
93     public synchronized String toString() {
94         String str = null;
95
96         final Connection conn = this.getInnermostDelegateInternal();
97         if (conn != null) {
98             try {
99                 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> thrownList = 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                         thrownList.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                         thrownList.add(e);
642                     }
643                 }
644             }
645             clearTrace();
646             if (!thrownList.isEmpty()) {
647                 throw new SQLExceptionList(thrownList);
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 }
1002