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