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 private final boolean fastFailValidation;
81
82 private final Lock lock = new ReentrantLock();
83
84
85
86
87
88
89
90
91
92
93 public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
94 final ObjectName jmxName) {
95 this(conn, pool, jmxName, null, true);
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
113 final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
114 final boolean fastFailValidation) {
115 super(conn);
116 this.pool = pool;
117 this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName);
118 this.disconnectionSqlCodes = disconnectSqlCodes;
119 this.fastFailValidation = fastFailValidation;
120
121 if (jmxObjectName != null) {
122 try {
123 MBEAN_SERVER.registerMBean(this, jmxObjectName);
124 } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ignored) {
125
126 }
127 }
128 }
129
130
131
132
133
134
135 @Override
136 public void abort(final Executor executor) throws SQLException {
137 if (jmxObjectName != null) {
138 jmxObjectName.unregisterMBean();
139 }
140 super.abort(executor);
141 }
142
143
144
145
146 @Override
147 public void close() throws SQLException {
148 lock.lock();
149 try {
150 if (isClosedInternal()) {
151
152 return;
153 }
154
155 boolean isUnderlyingConnectionClosed;
156 try {
157 isUnderlyingConnectionClosed = getDelegateInternal().isClosed();
158 } catch (final SQLException e) {
159 try {
160 pool.invalidateObject(this);
161 } catch (final IllegalStateException ise) {
162
163 passivate();
164 getInnermostDelegate().close();
165 } catch (final Exception ignored) {
166
167 }
168 throw new SQLException("Cannot close connection (isClosed check failed)", e);
169 }
170
171
172
173
174
175
176 if (isUnderlyingConnectionClosed) {
177
178
179 try {
180 pool.invalidateObject(this);
181 } catch (final IllegalStateException e) {
182
183 passivate();
184 getInnermostDelegate().close();
185 } catch (final Exception e) {
186 throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
187 }
188 } else {
189
190
191 try {
192 pool.returnObject(this);
193 } catch (final IllegalStateException e) {
194
195 passivate();
196 getInnermostDelegate().close();
197 } catch (final SQLException | RuntimeException e) {
198 throw e;
199 } catch (final Exception e) {
200 throw new SQLException("Cannot close connection (return to pool failed)", e);
201 }
202 }
203 } finally {
204 lock.unlock();
205 }
206 }
207
208
209
210
211
212 public Collection<String> getDisconnectionSqlCodes() {
213 return disconnectionSqlCodes;
214 }
215
216
217
218
219 @Override
220 public String getToString() {
221 return toString();
222 }
223
224 @Override
225 protected void handleException(final SQLException e) throws SQLException {
226 fatalSqlExceptionThrown |= isFatalException(e);
227 super.handleException(e);
228 }
229
230
231
232
233
234
235
236
237 @Override
238 public boolean isClosed() throws SQLException {
239 if (isClosedInternal()) {
240 return true;
241 }
242
243 if (getDelegateInternal().isClosed()) {
244
245
246
247 close();
248 return true;
249 }
250
251 return false;
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266 boolean isDisconnectionSqlException(final SQLException e) {
267 boolean fatalException = false;
268 final String sqlState = e.getSQLState();
269 if (sqlState != null) {
270 fatalException = disconnectionSqlCodes == null
271 ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.getDisconnectionSqlCodes().contains(sqlState)
272 : disconnectionSqlCodes.contains(sqlState);
273 }
274 return fatalException;
275 }
276
277
278
279
280
281 public boolean isFastFailValidation() {
282 return fastFailValidation;
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298 boolean isFatalException(final SQLException e) {
299 boolean fatalException = isDisconnectionSqlException(e);
300 if (!fatalException) {
301 SQLException parentException = e;
302 SQLException nextException = e.getNextException();
303 while (nextException != null && nextException != parentException && !fatalException) {
304 fatalException = isDisconnectionSqlException(nextException);
305 parentException = nextException;
306 nextException = parentException.getNextException();
307 }
308 }
309 return fatalException;
310 }
311
312 @Override
313 protected void passivate() throws SQLException {
314 super.passivate();
315 setClosedInternal(true);
316 if (getDelegateInternal() instanceof PoolingConnection) {
317 ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool();
318 }
319 }
320
321
322
323
324 @Override
325 public void reallyClose() throws SQLException {
326 if (jmxObjectName != null) {
327 jmxObjectName.unregisterMBean();
328 }
329
330 if (validationPreparedStatement != null) {
331 Utils.closeQuietly((AutoCloseable) validationPreparedStatement);
332 }
333
334 super.closeInternal();
335 }
336
337 @Override
338 public void setLastUsed() {
339 super.setLastUsed();
340 if (pool instanceof GenericObjectPool<?>) {
341 final GenericObjectPool<PoolableConnection> gop = (GenericObjectPool<PoolableConnection>) pool;
342 if (gop.isAbandonedConfig()) {
343 gop.use(this);
344 }
345 }
346 }
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367 public void validate(final String sql, Duration timeoutDuration) throws SQLException {
368 if (fastFailValidation && fatalSqlExceptionThrown) {
369 throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
370 }
371
372 if (sql == null || sql.isEmpty()) {
373 if (timeoutDuration.isNegative()) {
374 timeoutDuration = Duration.ZERO;
375 }
376 if (!isValid(timeoutDuration)) {
377 throw new SQLException("isValid() returned false");
378 }
379 return;
380 }
381
382 if (!sql.equals(lastValidationSql)) {
383 lastValidationSql = sql;
384
385
386 validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql);
387 }
388
389 if (timeoutDuration.compareTo(Duration.ZERO) > 0) {
390 validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds());
391 }
392
393 try (ResultSet rs = validationPreparedStatement.executeQuery()) {
394 if (!rs.next()) {
395 throw new SQLException("validationQuery didn't return a row");
396 }
397 } catch (final SQLException sqle) {
398 throw sqle;
399 }
400 }
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421 @Deprecated
422 public void validate(final String sql, final int timeoutSeconds) throws SQLException {
423 validate(sql, Duration.ofSeconds(timeoutSeconds));
424 }
425 }