1
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
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
71 static {
72 LOGGER = LoggerFactory.getLogger(ProxyConnection.class);
73
74 ERROR_STATES = new HashSet<>();
75 ERROR_STATES.add("0A000");
76 ERROR_STATES.add("57P01");
77 ERROR_STATES.add("57P02");
78 ERROR_STATES.add("57P03");
79 ERROR_STATES.add("01002");
80 ERROR_STATES.add("JZ0C0");
81 ERROR_STATES.add("JZ0C1");
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
105 @Override
106 public final String toString()
107 {
108 return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
109 }
110
111
112
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
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
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
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
243
244
245
246 @Override
247 public final void close() throws SQLException
248 {
249
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
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
283 @Override
284 @SuppressWarnings("RedundantThrows")
285 public boolean isClosed() throws SQLException
286 {
287 return (delegate == ClosedConnection.CLOSED_CONNECTION);
288 }
289
290
291 @Override
292 public Statement createStatement() throws SQLException
293 {
294 return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement()));
295 }
296
297
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
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
313 @Override
314 public CallableStatement prepareCall(String sql) throws SQLException
315 {
316 return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql)));
317 }
318
319
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
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
334 @Override
335 public PreparedStatement prepareStatement(String sql) throws SQLException
336 {
337 return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql)));
338 }
339
340
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
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
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
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
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
376 @Override
377 public DatabaseMetaData getMetaData() throws SQLException
378 {
379 markCommitStateDirty();
380 return ProxyFactory.getProxyDatabaseMetaData(this, delegate.getMetaData());
381 }
382
383
384 @Override
385 public void commit() throws SQLException
386 {
387 delegate.commit();
388 isCommitStateDirty = false;
389 lastAccess = currentTime();
390 }
391
392
393 @Override
394 public void rollback() throws SQLException
395 {
396 delegate.rollback();
397 isCommitStateDirty = false;
398 lastAccess = currentTime();
399 }
400
401
402 @Override
403 public void rollback(Savepoint savepoint) throws SQLException
404 {
405 delegate.rollback(savepoint);
406 isCommitStateDirty = false;
407 lastAccess = currentTime();
408 }
409
410
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
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
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
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
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
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
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
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
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