1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.dbcp2;
18
19 import java.lang.management.ManagementFactory;
20 import java.sql.Connection;
21 import java.sql.PreparedStatement;
22 import java.sql.ResultSet;
23 import java.sql.SQLException;
24 import java.time.Duration;
25 import java.util.Collection;
26 import java.util.concurrent.Executor;
27 import java.util.concurrent.locks.Lock;
28 import java.util.concurrent.locks.ReentrantLock;
29
30 import javax.management.InstanceAlreadyExistsException;
31 import javax.management.MBeanRegistrationException;
32 import javax.management.MBeanServer;
33 import javax.management.NotCompliantMBeanException;
34 import javax.management.ObjectName;
35
36 import org.apache.commons.pool2.ObjectPool;
37 import org.apache.commons.pool2.impl.GenericObjectPool;
38
39
40
41
42
43
44
45 public class PoolableConnection extends DelegatingConnection<Connection> implements PoolableConnectionMXBean {
46
47 private static MBeanServer MBEAN_SERVER;
48
49 static {
50 try {
51 MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
52 } catch (final NoClassDefFoundError | Exception ignored) {
53
54 }
55 }
56
57
58 private final ObjectPool<PoolableConnection> pool;
59
60 private final ObjectNameWrapper jmxObjectName;
61
62
63
64 private PreparedStatement validationPreparedStatement;
65 private String lastValidationSql;
66
67
68
69
70
71 private boolean fatalSqlExceptionThrown;
72
73
74
75
76
77 private final Collection<String> disconnectionSqlCodes;
78
79
80
81
82
83
84 private final Collection<String> disconnectionIgnoreSqlCodes;
85
86
87
88 private final boolean fastFailValidation;
89
90 private final Lock lock = new ReentrantLock();
91
92
93
94
95
96
97
98
99
100
101 public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
102 final ObjectName jmxName) {
103 this(conn, pool, jmxName, null, true);
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
121 final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
122 final boolean fastFailValidation) {
123 this(conn, pool, jmxObjectName, disconnectSqlCodes, null, fastFailValidation);
124 }
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
145 final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
146 final Collection<String> disconnectionIgnoreSqlCodes, final boolean fastFailValidation) {
147 super(conn);
148 this.pool = pool;
149 this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName);
150 this.disconnectionSqlCodes = disconnectSqlCodes;
151 this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes;
152 this.fastFailValidation = fastFailValidation;
153
154 if (jmxObjectName != null) {
155 try {
156 MBEAN_SERVER.registerMBean(this, jmxObjectName);
157 } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ignored) {
158
159 }
160 }
161 }
162
163
164
165
166
167
168 @Override
169 public void abort(final Executor executor) throws SQLException {
170 if (jmxObjectName != null) {
171 jmxObjectName.unregisterMBean();
172 }
173 super.abort(executor);
174 }
175
176
177
178
179 @Override
180 public void close() throws SQLException {
181 lock.lock();
182 try {
183 if (isClosedInternal()) {
184
185 return;
186 }
187
188 boolean isUnderlyingConnectionClosed;
189 try {
190 isUnderlyingConnectionClosed = getDelegateInternal().isClosed();
191 } catch (final SQLException e) {
192 try {
193 pool.invalidateObject(this);
194 } catch (final IllegalStateException ise) {
195
196 passivate();
197 getInnermostDelegate().close();
198 } catch (final Exception ignored) {
199
200 }
201 throw new SQLException("Cannot close connection (isClosed check failed)", e);
202 }
203
204
205
206
207
208
209 if (isUnderlyingConnectionClosed) {
210
211
212 try {
213 pool.invalidateObject(this);
214 } catch (final IllegalStateException e) {
215
216 passivate();
217 getInnermostDelegate().close();
218 } catch (final Exception e) {
219 throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
220 }
221 } else {
222
223
224 try {
225 pool.returnObject(this);
226 } catch (final IllegalStateException e) {
227
228 passivate();
229 getInnermostDelegate().close();
230 } catch (final SQLException | RuntimeException e) {
231 throw e;
232 } catch (final Exception e) {
233 throw new SQLException("Cannot close connection (return to pool failed)", e);
234 }
235 }
236 } finally {
237 lock.unlock();
238 }
239 }
240
241
242
243
244
245 public Collection<String> getDisconnectionSqlCodes() {
246 return disconnectionSqlCodes;
247 }
248
249
250
251
252 @Override
253 public String getToString() {
254 return toString();
255 }
256
257 @Override
258 protected void handleException(final SQLException e) throws SQLException {
259 fatalSqlExceptionThrown |= isFatalException(e);
260 super.handleException(e);
261 }
262
263
264
265
266
267
268
269
270 @Override
271 public boolean isClosed() throws SQLException {
272 if (isClosedInternal()) {
273 return true;
274 }
275
276 if (getDelegateInternal().isClosed()) {
277
278
279
280 close();
281 return true;
282 }
283
284 return false;
285 }
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300 boolean isDisconnectionSqlException(final SQLException e) {
301 boolean fatalException = false;
302 final String sqlState = e.getSQLState();
303 if (sqlState != null) {
304 if (disconnectionIgnoreSqlCodes != null && disconnectionIgnoreSqlCodes.contains(sqlState)) {
305 return false;
306 }
307 fatalException = disconnectionSqlCodes == null
308 ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.isDisconnectionSqlCode(sqlState)
309 : disconnectionSqlCodes.contains(sqlState);
310 }
311 return fatalException;
312 }
313
314
315
316
317
318 public boolean isFastFailValidation() {
319 return fastFailValidation;
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335 boolean isFatalException(final SQLException e) {
336 boolean fatalException = isDisconnectionSqlException(e);
337 if (!fatalException) {
338 SQLException parentException = e;
339 SQLException nextException = e.getNextException();
340 while (nextException != null && nextException != parentException && !fatalException) {
341 fatalException = isDisconnectionSqlException(nextException);
342 parentException = nextException;
343 nextException = parentException.getNextException();
344 }
345 }
346 return fatalException;
347 }
348
349 @Override
350 protected void passivate() throws SQLException {
351 super.passivate();
352 setClosedInternal(true);
353 if (getDelegateInternal() instanceof PoolingConnection) {
354 ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool();
355 }
356 }
357
358
359
360
361 @Override
362 public void reallyClose() throws SQLException {
363 if (jmxObjectName != null) {
364 jmxObjectName.unregisterMBean();
365 }
366
367 if (validationPreparedStatement != null) {
368 Utils.closeQuietly((AutoCloseable) validationPreparedStatement);
369 }
370
371 super.closeInternal();
372 }
373
374 @Override
375 public void setLastUsed() {
376 super.setLastUsed();
377 if (pool instanceof GenericObjectPool<?>) {
378 final GenericObjectPool<PoolableConnection> gop = (GenericObjectPool<PoolableConnection>) pool;
379 if (gop.isAbandonedConfig()) {
380 gop.use(this);
381 }
382 }
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 public void validate(final String sql, Duration timeoutDuration) throws SQLException {
405 if (fastFailValidation && fatalSqlExceptionThrown) {
406 throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
407 }
408
409 if (sql == null || sql.isEmpty()) {
410 if (timeoutDuration.isNegative()) {
411 timeoutDuration = Duration.ZERO;
412 }
413 if (!isValid(timeoutDuration)) {
414 throw new SQLException("isValid() returned false");
415 }
416 return;
417 }
418
419 if (!sql.equals(lastValidationSql)) {
420 lastValidationSql = sql;
421
422
423 validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql);
424 }
425
426 if (timeoutDuration.compareTo(Duration.ZERO) > 0) {
427 validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds());
428 }
429
430 try (ResultSet rs = validationPreparedStatement.executeQuery()) {
431 if (!rs.next()) {
432 throw new SQLException("validationQuery didn't return a row");
433 }
434 } catch (final SQLException sqle) {
435 throw sqle;
436 }
437 }
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458 @Deprecated
459 public void validate(final String sql, final int timeoutSeconds) throws SQLException {
460 validate(sql, Duration.ofSeconds(timeoutSeconds));
461 }
462 }