1 /*
2  * Copyright (C) 2013, 2014 Brett Wooldridge
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package com.zaxxer.hikari.pool;
18
19 import com.zaxxer.hikari.SQLExceptionOverride;
20 import com.zaxxer.hikari.util.FastList;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 import java.lang.reflect.InvocationHandler;
25 import java.lang.reflect.Proxy;
26 import java.sql.*;
27 import java.util.HashSet;
28 import java.util.Set;
29 import java.util.concurrent.Executor;
30
31 import static com.zaxxer.hikari.SQLExceptionOverride.Override.DO_NOT_EVICT;
32 import static com.zaxxer.hikari.util.ClockSource.currentTime;
33
34 /**
35  * This is the proxy class for java.sql.Connection.
36  *
37  * @author Brett Wooldridge
38  */

39 public abstract class ProxyConnection implements Connection
40 {
41    static final int DIRTY_BIT_READONLY   = 0b000001;
42    static final int DIRTY_BIT_AUTOCOMMIT = 0b000010;
43    static final int DIRTY_BIT_ISOLATION  = 0b000100;
44    static final int DIRTY_BIT_CATALOG    = 0b001000;
45    static final int DIRTY_BIT_NETTIMEOUT = 0b010000;
46    static final int DIRTY_BIT_SCHEMA     = 0b100000;
47
48    private static final Logger LOGGER;
49    private static final Set<String> ERROR_STATES;
50    private static final Set<Integer> ERROR_CODES;
51
52    @SuppressWarnings("WeakerAccess")
53    protected Connection delegate;
54
55    private final PoolEntry poolEntry;
56    private final ProxyLeakTask leakTask;
57    private final FastList<Statement> openStatements;
58
59    private int dirtyBits;
60    private long lastAccess;
61    private boolean isCommitStateDirty;
62
63    private boolean isReadOnly;
64    private boolean isAutoCommit;
65    private int networkTimeout;
66    private int transactionIsolation;
67    private String dbcatalog;
68    private String dbschema;
69
70    // static initializer
71    static {
72       LOGGER = LoggerFactory.getLogger(ProxyConnection.class);
73
74       ERROR_STATES = new HashSet<>();
75       ERROR_STATES.add("0A000"); // FEATURE UNSUPPORTED
76       ERROR_STATES.add("57P01"); // ADMIN SHUTDOWN
77       ERROR_STATES.add("57P02"); // CRASH SHUTDOWN
78       ERROR_STATES.add("57P03"); // CANNOT CONNECT NOW
79       ERROR_STATES.add("01002"); // SQL92 disconnect error
80       ERROR_STATES.add("JZ0C0"); // Sybase disconnect error
81       ERROR_STATES.add("JZ0C1"); // Sybase disconnect error
82
83       ERROR_CODES = new HashSet<>();
84       ERROR_CODES.add(500150);
85       ERROR_CODES.add(2399);
86    }
87
88    protected ProxyConnection(final PoolEntry poolEntry,
89                              final Connection connection,
90                              final FastList<Statement> openStatements,
91                              final ProxyLeakTask leakTask,
92                              final long now,
93                              final boolean isReadOnly,
94                              final boolean isAutoCommit) {
95       this.poolEntry = poolEntry;
96       this.delegate = connection;
97       this.openStatements = openStatements;
98       this.leakTask = leakTask;
99       this.lastAccess = now;
100       this.isReadOnly = isReadOnly;
101       this.isAutoCommit = isAutoCommit;
102    }
103
104    /** {@inheritDoc} */
105    @Override
106    public final String toString()
107    {
108       return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
109    }
110
111    // ***********************************************************************
112    //                     Connection State Accessors
113    // ***********************************************************************
114
115    final boolean getAutoCommitState()
116    {
117       return isAutoCommit;
118    }
119
120    final String getCatalogState()
121    {
122       return dbcatalog;
123    }
124
125    final String getSchemaState()
126    {
127       return dbschema;
128    }
129
130    final int getTransactionIsolationState()
131    {
132       return transactionIsolation;
133    }
134
135    final boolean getReadOnlyState()
136    {
137       return isReadOnly;
138    }
139
140    final int getNetworkTimeoutState()
141    {
142       return networkTimeout;
143    }
144
145    // ***********************************************************************
146    //                          Internal methods
147    // ***********************************************************************
148
149    final PoolEntry getPoolEntry()
150    {
151       return poolEntry;
152    }
153
154    @SuppressWarnings("ConstantConditions")
155    final SQLException checkException(SQLException sqle)
156    {
157       boolean evict = false;
158       SQLException nse = sqle;
159       final SQLExceptionOverride exceptionOverride = poolEntry.getPoolBase().exceptionOverride;
160       for (int depth = 0; delegate != ClosedConnection.CLOSED_CONNECTION && nse != null && depth < 10; depth++) {
161          final String sqlState = nse.getSQLState();
162          if (sqlState != null && sqlState.startsWith("08")
163              || nse instanceof SQLTimeoutException
164              || ERROR_STATES.contains(sqlState)
165              || ERROR_CODES.contains(nse.getErrorCode())) {
166
167             if (exceptionOverride != null && exceptionOverride.adjudicate(nse) == DO_NOT_EVICT) {
168                break;
169             }
170
171             // broken connection
172             evict = true;
173             break;
174          }
175          else {
176             nse = nse.getNextException();
177          }
178       }
179
180       if (evict) {
181          SQLException exception = (nse != null) ? nse : sqle;
182          LOGGER.warn("{} - Connection {} marked as broken because of SQLSTATE({}), ErrorCode({})",
183             poolEntry.getPoolName(), delegate, exception.getSQLState(), exception.getErrorCode(), exception);
184          leakTask.cancel();
185          poolEntry.evict("(connection is broken)");
186          delegate = ClosedConnection.CLOSED_CONNECTION;
187       }
188
189       return sqle;
190    }
191
192    final synchronized void untrackStatement(final Statement statement)
193    {
194       openStatements.remove(statement);
195    }
196
197    final void markCommitStateDirty()
198    {
199       if (isAutoCommit) {
200          lastAccess = currentTime();
201       }
202       else {
203          isCommitStateDirty = true;
204       }
205    }
206
207    void cancelLeakTask()
208    {
209       leakTask.cancel();
210    }
211
212    private synchronized <T extends Statement> T trackStatement(final T statement)
213    {
214       openStatements.add(statement);
215
216       return statement;
217    }
218
219    @SuppressWarnings("EmptyTryBlock")
220    private synchronized void closeStatements()
221    {
222       final int size = openStatements.size();
223       if (size > 0) {
224          for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) {
225             try (Statement ignored = openStatements.get(i)) {
226                // automatic resource cleanup
227             }
228             catch (SQLException e) {
229                LOGGER.warn("{} - Connection {} marked as broken because of an exception closing open statements during Connection.close()",
230                            poolEntry.getPoolName(), delegate);
231                leakTask.cancel();
232                poolEntry.evict("(exception closing Statements during Connection.close())");
233                delegate = ClosedConnection.CLOSED_CONNECTION;
234             }
235          }
236
237          openStatements.clear();
238       }
239    }
240
241    // **********************************************************************
242    //              "Overridden" java.sql.Connection Methods
243    // **********************************************************************
244
245    /** {@inheritDoc} */
246    @Override
247    public final void close() throws SQLException
248    {
249       // Closing statements can cause connection eviction, so this must run before the conditional below
250       closeStatements();
251
252       if (delegate != ClosedConnection.CLOSED_CONNECTION) {
253          leakTask.cancel();
254
255          try {
256             if (isCommitStateDirty && !isAutoCommit) {
257                delegate.rollback();
258                lastAccess = currentTime();
259                LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate);
260             }
261
262             if (dirtyBits != 0) {
263                poolEntry.resetConnectionState(this, dirtyBits);
264                lastAccess = currentTime();
265             }
266
267             delegate.clearWarnings();
268          }
269          catch (SQLException e) {
270             // when connections are aborted, exceptions are often thrown that should not reach the application
271             if (!poolEntry.isMarkedEvicted()) {
272                throw checkException(e);
273             }
274          }
275          finally {
276             delegate = ClosedConnection.CLOSED_CONNECTION;
277             poolEntry.recycle(lastAccess);
278          }
279       }
280    }
281
282    /** {@inheritDoc} */
283    @Override
284    @SuppressWarnings("RedundantThrows")
285    public boolean isClosed() throws SQLException
286    {
287       return (delegate == ClosedConnection.CLOSED_CONNECTION);
288    }
289
290    /** {@inheritDoc} */
291    @Override
292    public Statement createStatement() throws SQLException
293    {
294       return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement()));
295    }
296
297    /** {@inheritDoc} */
298    @Override
299    public Statement createStatement(int resultSetType, int concurrency) throws SQLException
300    {
301       return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency)));
302    }
303
304    /** {@inheritDoc} */
305    @Override
306    public Statement createStatement(int resultSetType, int concurrency, int holdability) throws SQLException
307    {
308       return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency, holdability)));
309    }
310
311
312    /** {@inheritDoc} */
313    @Override
314    public CallableStatement prepareCall(String sql) throws SQLException
315    {
316       return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql)));
317    }
318
319    /** {@inheritDoc} */
320    @Override
321    public CallableStatement prepareCall(String sql, int resultSetType, int concurrency) throws SQLException
322    {
323       return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency)));
324    }
325
326    /** {@inheritDoc} */
327    @Override
328    public CallableStatement prepareCall(String sql, int resultSetType, int concurrency, int holdability) throws SQLException
329    {
330       return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency, holdability)));
331    }
332
333    /** {@inheritDoc} */
334    @Override
335    public PreparedStatement prepareStatement(String sql) throws SQLException
336    {
337       return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql)));
338    }
339
340    /** {@inheritDoc} */
341    @Override
342    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
343    {
344       return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, autoGeneratedKeys)));
345    }
346
347    /** {@inheritDoc} */
348    @Override
349    public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency) throws SQLException
350    {
351       return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency)));
352    }
353
354    /** {@inheritDoc} */
355    @Override
356    public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency, int holdability) throws SQLException
357    {
358       return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency, holdability)));
359    }
360
361    /** {@inheritDoc} */
362    @Override
363    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException
364    {
365       return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnIndexes)));
366    }
367
368    /** {@inheritDoc} */
369    @Override
370    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
371    {
372       return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnNames)));
373    }
374
375    /** {@inheritDoc} */
376    @Override
377    public DatabaseMetaData getMetaData() throws SQLException
378    {
379       markCommitStateDirty();
380       return ProxyFactory.getProxyDatabaseMetaData(this, delegate.getMetaData());
381    }
382
383    /** {@inheritDoc} */
384    @Override
385    public void commit() throws SQLException
386    {
387       delegate.commit();
388       isCommitStateDirty = false;
389       lastAccess = currentTime();
390    }
391
392    /** {@inheritDoc} */
393    @Override
394    public void rollback() throws SQLException
395    {
396       delegate.rollback();
397       isCommitStateDirty = false;
398       lastAccess = currentTime();
399    }
400
401    /** {@inheritDoc} */
402    @Override
403    public void rollback(Savepoint savepoint) throws SQLException
404    {
405       delegate.rollback(savepoint);
406       isCommitStateDirty = false;
407       lastAccess = currentTime();
408    }
409
410    /** {@inheritDoc} */
411    @Override
412    public void setAutoCommit(boolean autoCommit) throws SQLException
413    {
414       delegate.setAutoCommit(autoCommit);
415       isAutoCommit = autoCommit;
416       dirtyBits |= DIRTY_BIT_AUTOCOMMIT;
417    }
418
419    /** {@inheritDoc} */
420    @Override
421    public void setReadOnly(boolean readOnly) throws SQLException
422    {
423       delegate.setReadOnly(readOnly);
424       isReadOnly = readOnly;
425       isCommitStateDirty = false;
426       dirtyBits |= DIRTY_BIT_READONLY;
427    }
428
429    /** {@inheritDoc} */
430    @Override
431    public void setTransactionIsolation(int level) throws SQLException
432    {
433       delegate.setTransactionIsolation(level);
434       transactionIsolation = level;
435       dirtyBits |= DIRTY_BIT_ISOLATION;
436    }
437
438    /** {@inheritDoc} */
439    @Override
440    public void setCatalog(String catalog) throws SQLException
441    {
442       delegate.setCatalog(catalog);
443       dbcatalog = catalog;
444       dirtyBits |= DIRTY_BIT_CATALOG;
445    }
446
447    /** {@inheritDoc} */
448    @Override
449    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException
450    {
451       delegate.setNetworkTimeout(executor, milliseconds);
452       networkTimeout = milliseconds;
453       dirtyBits |= DIRTY_BIT_NETTIMEOUT;
454    }
455
456    /** {@inheritDoc} */
457    @Override
458    public void setSchema(String schema) throws SQLException
459    {
460       delegate.setSchema(schema);
461       dbschema = schema;
462       dirtyBits |= DIRTY_BIT_SCHEMA;
463    }
464
465    /** {@inheritDoc} */
466    @Override
467    public final boolean isWrapperFor(Class<?> iface) throws SQLException
468    {
469       return iface.isInstance(delegate) || (delegate != null && delegate.isWrapperFor(iface));
470    }
471
472    /** {@inheritDoc} */
473    @Override
474    @SuppressWarnings("unchecked")
475    public final <T> T unwrap(Class<T> iface) throws SQLException
476    {
477       if (iface.isInstance(delegate)) {
478          return (T) delegate;
479       }
480       else if (delegate != null) {
481           return delegate.unwrap(iface);
482       }
483
484       throw new SQLException("Wrapped connection is not an instance of " + iface);
485    }
486
487    // **********************************************************************
488    //                         Private classes
489    // **********************************************************************
490
491    private static final class ClosedConnection
492    {
493       static final Connection CLOSED_CONNECTION = getClosedConnection();
494
495       private static Connection getClosedConnection()
496       {
497          InvocationHandler handler = (proxy, method, args) -> {
498             final String methodName = method.getName();
499             if ("isClosed".equals(methodName)) {
500                return Boolean.TRUE;
501             }
502             else if ("isValid".equals(methodName)) {
503                return Boolean.FALSE;
504             }
505             if ("abort".equals(methodName)) {
506                return Void.TYPE;
507             }
508             if ("close".equals(methodName)) {
509                return Void.TYPE;
510             }
511             else if ("toString".equals(methodName)) {
512                return ClosedConnection.class.getCanonicalName();
513             }
514
515             throw new SQLException("Connection is closed");
516          };
517
518          return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);
519       }
520    }
521 }
522