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                boolean connectionIsClosed;
228                try {
229                    connectionIsClosed = connection.isClosed();
230                } catch (final SQLException e) {
231                    // not sure what the state is, so assume the connection is open.
232                    connectionIsClosed = false;
233                }
234                try {
235                    // DBCP-512: Avoid exceptions when closing a connection in mutli-threaded use case.
236                    // Avoid closing again, which should be a no-op, but some drivers like H2 throw an exception when
237                    // closing from multiple threads.
238                    if (!connectionIsClosed) {
239                        connection.close();
240                    }
241                } finally {
242                    closed = true;
243                }
244            } else {
245                closed = true;
246            }
247        }
248    }
249
250    protected void handleException(final SQLException e) throws SQLException {
251        throw e;
252    }
253
254    private void initializeStatement(final DelegatingStatement ds) throws SQLException {
255        if (defaultQueryTimeoutSeconds != null && defaultQueryTimeoutSeconds.intValue() != ds.getQueryTimeout()) {
256            ds.setQueryTimeout(defaultQueryTimeoutSeconds.intValue());
257        }
258    }
259
260    @Override
261    public Statement createStatement() throws SQLException {
262        checkOpen();
263        try {
264            final DelegatingStatement ds = new DelegatingStatement(this, connection.createStatement());
265            initializeStatement(ds);
266            return ds;
267        } catch (final SQLException e) {
268            handleException(e);
269            return null;
270        }
271    }
272
273    @Override
274    public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
275        checkOpen();
276        try {
277            final DelegatingStatement ds = new DelegatingStatement(this,
278                    connection.createStatement(resultSetType, resultSetConcurrency));
279            initializeStatement(ds);
280            return ds;
281        } catch (final SQLException e) {
282            handleException(e);
283            return null;
284        }
285    }
286
287    @Override
288    public PreparedStatement prepareStatement(final String sql) throws SQLException {
289        checkOpen();
290        try {
291            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
292                    connection.prepareStatement(sql));
293            initializeStatement(dps);
294            return dps;
295        } catch (final SQLException e) {
296            handleException(e);
297            return null;
298        }
299    }
300
301    @Override
302    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
303            throws SQLException {
304        checkOpen();
305        try {
306            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
307                    connection.prepareStatement(sql, resultSetType, resultSetConcurrency));
308            initializeStatement(dps);
309            return dps;
310        } catch (final SQLException e) {
311            handleException(e);
312            return null;
313        }
314    }
315
316    @Override
317    public CallableStatement prepareCall(final String sql) throws SQLException {
318        checkOpen();
319        try {
320            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this, connection.prepareCall(sql));
321            initializeStatement(dcs);
322            return dcs;
323        } catch (final SQLException e) {
324            handleException(e);
325            return null;
326        }
327    }
328
329    @Override
330    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
331            throws SQLException {
332        checkOpen();
333        try {
334            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this,
335                    connection.prepareCall(sql, resultSetType, resultSetConcurrency));
336            initializeStatement(dcs);
337            return dcs;
338        } catch (final SQLException e) {
339            handleException(e);
340            return null;
341        }
342    }
343
344    @Override
345    public void clearWarnings() throws SQLException {
346        checkOpen();
347        try {
348            connection.clearWarnings();
349        } catch (final SQLException e) {
350            handleException(e);
351        }
352    }
353
354    @Override
355    public void commit() throws SQLException {
356        checkOpen();
357        try {
358            connection.commit();
359        } catch (final SQLException e) {
360            handleException(e);
361        }
362    }
363
364    /**
365     * Returns the state caching flag.
366     *
367     * @return the state caching flag
368     */
369    public boolean getCacheState() {
370        return cacheState;
371    }
372
373    @Override
374    public boolean getAutoCommit() throws SQLException {
375        checkOpen();
376        if (cacheState && autoCommitCached != null) {
377            return autoCommitCached.booleanValue();
378        }
379        try {
380            autoCommitCached = Boolean.valueOf(connection.getAutoCommit());
381            return autoCommitCached.booleanValue();
382        } catch (final SQLException e) {
383            handleException(e);
384            return false;
385        }
386    }
387
388    @Override
389    public String getCatalog() throws SQLException {
390        checkOpen();
391        try {
392            return connection.getCatalog();
393        } catch (final SQLException e) {
394            handleException(e);
395            return null;
396        }
397    }
398
399    @Override
400    public DatabaseMetaData getMetaData() throws SQLException {
401        checkOpen();
402        try {
403            return new DelegatingDatabaseMetaData(this, connection.getMetaData());
404        } catch (final SQLException e) {
405            handleException(e);
406            return null;
407        }
408    }
409
410    @Override
411    public int getTransactionIsolation() throws SQLException {
412        checkOpen();
413        try {
414            return connection.getTransactionIsolation();
415        } catch (final SQLException e) {
416            handleException(e);
417            return -1;
418        }
419    }
420
421    @Override
422    public Map<String, Class<?>> getTypeMap() throws SQLException {
423        checkOpen();
424        try {
425            return connection.getTypeMap();
426        } catch (final SQLException e) {
427            handleException(e);
428            return null;
429        }
430    }
431
432    @Override
433    public SQLWarning getWarnings() throws SQLException {
434        checkOpen();
435        try {
436            return connection.getWarnings();
437        } catch (final SQLException e) {
438            handleException(e);
439            return null;
440        }
441    }
442
443    @Override
444    public boolean isReadOnly() throws SQLException {
445        checkOpen();
446        if (cacheState && readOnlyCached != null) {
447            return readOnlyCached.booleanValue();
448        }
449        try {
450            readOnlyCached = Boolean.valueOf(connection.isReadOnly());
451            return readOnlyCached.booleanValue();
452        } catch (final SQLException e) {
453            handleException(e);
454            return false;
455        }
456    }
457
458    @Override
459    public String nativeSQL(final String sql) throws SQLException {
460        checkOpen();
461        try {
462            return connection.nativeSQL(sql);
463        } catch (final SQLException e) {
464            handleException(e);
465            return null;
466        }
467    }
468
469    @Override
470    public void rollback() throws SQLException {
471        checkOpen();
472        try {
473            connection.rollback();
474        } catch (final SQLException e) {
475            handleException(e);
476        }
477    }
478
479    /**
480     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
481     * <code>null</code> means that the driver default will be used.
482     *
483     * @return query timeout limit in seconds; zero means there is no limit.
484     */
485    public Integer getDefaultQueryTimeout() {
486        return defaultQueryTimeoutSeconds;
487    }
488
489    /**
490     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
491     * <code>null</code> means that the driver default will be used.
492     *
493     * @param defaultQueryTimeoutSeconds
494     *            the new query timeout limit in seconds; zero means there is no limit
495     */
496    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
497        this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
498    }
499
500    /**
501     * Sets the state caching flag.
502     *
503     * @param cacheState
504     *            The new value for the state caching flag
505     */
506    public void setCacheState(final boolean cacheState) {
507        this.cacheState = cacheState;
508    }
509
510    /**
511     * Can be used to clear cached state when it is known that the underlying connection may have been accessed
512     * directly.
513     */
514    public void clearCachedState() {
515        autoCommitCached = null;
516        readOnlyCached = null;
517        if (connection instanceof DelegatingConnection) {
518            ((DelegatingConnection<?>) connection).clearCachedState();
519        }
520    }
521
522    @Override
523    public void setAutoCommit(final boolean autoCommit) throws SQLException {
524        checkOpen();
525        try {
526            connection.setAutoCommit(autoCommit);
527            if (cacheState) {
528                autoCommitCached = Boolean.valueOf(autoCommit);
529            }
530        } catch (final SQLException e) {
531            autoCommitCached = null;
532            handleException(e);
533        }
534    }
535
536    @Override
537    public void setCatalog(final String catalog) throws SQLException {
538        checkOpen();
539        try {
540            connection.setCatalog(catalog);
541        } catch (final SQLException e) {
542            handleException(e);
543        }
544    }
545
546    @Override
547    public void setReadOnly(final boolean readOnly) throws SQLException {
548        checkOpen();
549        try {
550            connection.setReadOnly(readOnly);
551            if (cacheState) {
552                readOnlyCached = Boolean.valueOf(readOnly);
553            }
554        } catch (final SQLException e) {
555            readOnlyCached = null;
556            handleException(e);
557        }
558    }
559
560    @Override
561    public void setTransactionIsolation(final int level) throws SQLException {
562        checkOpen();
563        try {
564            connection.setTransactionIsolation(level);
565        } catch (final SQLException e) {
566            handleException(e);
567        }
568    }
569
570    @Override
571    public void setTypeMap(final Map<String, Class<?>> map) throws SQLException {
572        checkOpen();
573        try {
574            connection.setTypeMap(map);
575        } catch (final SQLException e) {
576            handleException(e);
577        }
578    }
579
580    @Override
581    public boolean isClosed() throws SQLException {
582        return closed || connection == null || connection.isClosed();
583    }
584
585    protected void checkOpen() throws SQLException {
586        if (closed) {
587            if (null != connection) {
588                String label = "";
589                try {
590                    label = connection.toString();
591                } catch (final Exception ex) {
592                    // ignore, leave label empty
593                }
594                throw new SQLException("Connection " + label + " is closed.");
595            }
596            throw new SQLException("Connection is null.");
597        }
598    }
599
600    protected void activate() {
601        closed = false;
602        setLastUsed();
603        if (connection instanceof DelegatingConnection) {
604            ((DelegatingConnection<?>) connection).activate();
605        }
606    }
607
608    protected void passivate() throws SQLException {
609        // The JDBC spec requires that a Connection close any open
610        // Statement's when it is closed.
611        // DBCP-288. Not all the traced objects will be statements
612        final List<AbandonedTrace> traces = getTrace();
613        if (traces != null && traces.size() > 0) {
614            final Iterator<AbandonedTrace> traceIter = traces.iterator();
615            while (traceIter.hasNext()) {
616                final Object trace = traceIter.next();
617                if (trace instanceof Statement) {
618                    ((Statement) trace).close();
619                } else if (trace instanceof ResultSet) {
620                    // DBCP-265: Need to close the result sets that are
621                    // generated via DatabaseMetaData
622                    ((ResultSet) trace).close();
623                }
624            }
625            clearTrace();
626        }
627        setLastUsed(0);
628    }
629
630    @Override
631    public int getHoldability() throws SQLException {
632        checkOpen();
633        try {
634            return connection.getHoldability();
635        } catch (final SQLException e) {
636            handleException(e);
637            return 0;
638        }
639    }
640
641    @Override
642    public void setHoldability(final int holdability) throws SQLException {
643        checkOpen();
644        try {
645            connection.setHoldability(holdability);
646        } catch (final SQLException e) {
647            handleException(e);
648        }
649    }
650
651    @Override
652    public Savepoint setSavepoint() throws SQLException {
653        checkOpen();
654        try {
655            return connection.setSavepoint();
656        } catch (final SQLException e) {
657            handleException(e);
658            return null;
659        }
660    }
661
662    @Override
663    public Savepoint setSavepoint(final String name) throws SQLException {
664        checkOpen();
665        try {
666            return connection.setSavepoint(name);
667        } catch (final SQLException e) {
668            handleException(e);
669            return null;
670        }
671    }
672
673    @Override
674    public void rollback(final Savepoint savepoint) throws SQLException {
675        checkOpen();
676        try {
677            connection.rollback(savepoint);
678        } catch (final SQLException e) {
679            handleException(e);
680        }
681    }
682
683    @Override
684    public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
685        checkOpen();
686        try {
687            connection.releaseSavepoint(savepoint);
688        } catch (final SQLException e) {
689            handleException(e);
690        }
691    }
692
693    @Override
694    public Statement createStatement(final int resultSetType, final int resultSetConcurrency,
695            final int resultSetHoldability) throws SQLException {
696        checkOpen();
697        try {
698            final DelegatingStatement ds = new DelegatingStatement(this,
699                    connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
700            initializeStatement(ds);
701            return ds;
702        } catch (final SQLException e) {
703            handleException(e);
704            return null;
705        }
706    }
707
708    @Override
709    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
710            final int resultSetHoldability) throws SQLException {
711        checkOpen();
712        try {
713            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
714                    connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
715            initializeStatement(dps);
716            return dps;
717        } catch (final SQLException e) {
718            handleException(e);
719            return null;
720        }
721    }
722
723    @Override
724    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
725            final int resultSetHoldability) throws SQLException {
726        checkOpen();
727        try {
728            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this,
729                    connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
730            initializeStatement(dcs);
731            return dcs;
732        } catch (final SQLException e) {
733            handleException(e);
734            return null;
735        }
736    }
737
738    @Override
739    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
740        checkOpen();
741        try {
742            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
743                    connection.prepareStatement(sql, autoGeneratedKeys));
744            initializeStatement(dps);
745            return dps;
746        } catch (final SQLException e) {
747            handleException(e);
748            return null;
749        }
750    }
751
752    @Override
753    public PreparedStatement prepareStatement(final String sql, final int columnIndexes[]) throws SQLException {
754        checkOpen();
755        try {
756            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
757                    connection.prepareStatement(sql, columnIndexes));
758            initializeStatement(dps);
759            return dps;
760        } catch (final SQLException e) {
761            handleException(e);
762            return null;
763        }
764    }
765
766    @Override
767    public PreparedStatement prepareStatement(final String sql, final String columnNames[]) throws SQLException {
768        checkOpen();
769        try {
770            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
771                    connection.prepareStatement(sql, columnNames));
772            initializeStatement(dps);
773            return dps;
774        } catch (final SQLException e) {
775            handleException(e);
776            return null;
777        }
778    }
779
780    @Override
781    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
782        if (iface.isAssignableFrom(getClass())) {
783            return true;
784        } else if (iface.isAssignableFrom(connection.getClass())) {
785            return true;
786        } else {
787            return connection.isWrapperFor(iface);
788        }
789    }
790
791    @Override
792    public <T> T unwrap(final Class<T> iface) throws SQLException {
793        if (iface.isAssignableFrom(getClass())) {
794            return iface.cast(this);
795        } else if (iface.isAssignableFrom(connection.getClass())) {
796            return iface.cast(connection);
797        } else {
798            return connection.unwrap(iface);
799        }
800    }
801
802    @Override
803    public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
804        checkOpen();
805        try {
806            return connection.createArrayOf(typeName, elements);
807        } catch (final SQLException e) {
808            handleException(e);
809            return null;
810        }
811    }
812
813    @Override
814    public Blob createBlob() throws SQLException {
815        checkOpen();
816        try {
817            return connection.createBlob();
818        } catch (final SQLException e) {
819            handleException(e);
820            return null;
821        }
822    }
823
824    @Override
825    public Clob createClob() throws SQLException {
826        checkOpen();
827        try {
828            return connection.createClob();
829        } catch (final SQLException e) {
830            handleException(e);
831            return null;
832        }
833    }
834
835    @Override
836    public NClob createNClob() throws SQLException {
837        checkOpen();
838        try {
839            return connection.createNClob();
840        } catch (final SQLException e) {
841            handleException(e);
842            return null;
843        }
844    }
845
846    @Override
847    public SQLXML createSQLXML() throws SQLException {
848        checkOpen();
849        try {
850            return connection.createSQLXML();
851        } catch (final SQLException e) {
852            handleException(e);
853            return null;
854        }
855    }
856
857    @Override
858    public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
859        checkOpen();
860        try {
861            return connection.createStruct(typeName, attributes);
862        } catch (final SQLException e) {
863            handleException(e);
864            return null;
865        }
866    }
867
868    @Override
869    public boolean isValid(final int timeoutSeconds) throws SQLException {
870        if (isClosed()) {
871            return false;
872        }
873        try {
874            return connection.isValid(timeoutSeconds);
875        } catch (final SQLException e) {
876            handleException(e);
877            return false;
878        }
879    }
880
881    @Override
882    public void setClientInfo(final String name, final String value) throws SQLClientInfoException {
883        try {
884            checkOpen();
885            connection.setClientInfo(name, value);
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 void setClientInfo(final Properties properties) throws SQLClientInfoException {
895        try {
896            checkOpen();
897            connection.setClientInfo(properties);
898        } catch (final SQLClientInfoException e) {
899            throw e;
900        } catch (final SQLException e) {
901            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
902        }
903    }
904
905    @Override
906    public Properties getClientInfo() throws SQLException {
907        checkOpen();
908        try {
909            return connection.getClientInfo();
910        } catch (final SQLException e) {
911            handleException(e);
912            return null;
913        }
914    }
915
916    @Override
917    public String getClientInfo(final String name) throws SQLException {
918        checkOpen();
919        try {
920            return connection.getClientInfo(name);
921        } catch (final SQLException e) {
922            handleException(e);
923            return null;
924        }
925    }
926
927    @Override
928    public void setSchema(final String schema) throws SQLException {
929        checkOpen();
930        try {
931            connection.setSchema(schema);
932        } catch (final SQLException e) {
933            handleException(e);
934        }
935    }
936
937    @Override
938    public String getSchema() throws SQLException {
939        checkOpen();
940        try {
941            return connection.getSchema();
942        } catch (final SQLException e) {
943            handleException(e);
944            return null;
945        }
946    }
947
948    @Override
949    public void abort(final Executor executor) throws SQLException {
950        checkOpen();
951        try {
952            connection.abort(executor);
953        } catch (final SQLException e) {
954            handleException(e);
955        }
956    }
957
958    @Override
959    public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException {
960        checkOpen();
961        try {
962            connection.setNetworkTimeout(executor, milliseconds);
963        } catch (final SQLException e) {
964            handleException(e);
965        }
966    }
967
968    @Override
969    public int getNetworkTimeout() throws SQLException {
970        checkOpen();
971        try {
972            return connection.getNetworkTimeout();
973        } catch (final SQLException e) {
974            handleException(e);
975            return 0;
976        }
977    }
978}