1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.dbcp2.datasources;
18
19 import java.sql.Connection;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22 import java.sql.Statement;
23 import java.time.Duration;
24 import java.util.Collections;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.concurrent.ConcurrentHashMap;
28
29 import javax.sql.ConnectionEvent;
30 import javax.sql.ConnectionEventListener;
31 import javax.sql.ConnectionPoolDataSource;
32 import javax.sql.PooledConnection;
33
34 import org.apache.commons.dbcp2.Utils;
35 import org.apache.commons.pool2.ObjectPool;
36 import org.apache.commons.pool2.PooledObject;
37 import org.apache.commons.pool2.PooledObjectFactory;
38 import org.apache.commons.pool2.impl.DefaultPooledObject;
39
40
41
42
43
44
45 final class CPDSConnectionFactory
46 implements PooledObjectFactory<PooledConnectionAndInfo>, ConnectionEventListener, PooledConnectionManager {
47
48 private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but I have no record of the underlying PooledConnection.";
49
50 private final ConnectionPoolDataSource cpds;
51 private final String validationQuery;
52 private final Duration validationQueryTimeoutDuration;
53 private final boolean rollbackAfterValidation;
54 private ObjectPool<PooledConnectionAndInfo> pool;
55 private UserPassKey userPassKey;
56 private Duration maxConnDuration = Duration.ofMillis(-1);
57
58
59
60
61 private final Set<PooledConnection> validatingSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
62
63
64
65
66 private final Map<PooledConnection, PooledConnectionAndInfo> pcMap = new ConcurrentHashMap<>();
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
88 final Duration validationQueryTimeoutDuration, final boolean rollbackAfterValidation, final String userName,
89 final char[] userPassword) {
90 this.cpds = cpds;
91 this.validationQuery = validationQuery;
92 this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
93 this.userPassKey = new UserPassKey(userName, userPassword);
94 this.rollbackAfterValidation = rollbackAfterValidation;
95 }
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final Duration validationQueryTimeoutDuration,
117 final boolean rollbackAfterValidation, final String userName, final String userPassword) {
118 this(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation, userName, Utils.toCharArray(userPassword));
119 }
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 @Deprecated
142 public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
143 final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation, final String userName,
144 final char[] userPassword) {
145 this.cpds = cpds;
146 this.validationQuery = validationQuery;
147 this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
148 this.userPassKey = new UserPassKey(userName, userPassword);
149 this.rollbackAfterValidation = rollbackAfterValidation;
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171 @Deprecated
172 public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final int validationQueryTimeoutSeconds,
173 final boolean rollbackAfterValidation, final String userName, final String userPassword) {
174 this(cpds, validationQuery, validationQueryTimeoutSeconds, rollbackAfterValidation, userName, Utils.toCharArray(userPassword));
175 }
176
177 @Override
178 public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
179 validateLifetime(p);
180 }
181
182
183
184
185
186 @Override
187 public void closePool(final String userName) throws SQLException {
188 synchronized (this) {
189 if (userName == null || !userName.equals(this.userPassKey.getUserName())) {
190 return;
191 }
192 }
193 try {
194 pool.close();
195 } catch (final Exception ex) {
196 throw new SQLException("Error closing connection pool", ex);
197 }
198 }
199
200
201
202
203
204
205 @Override
206 public void connectionClosed(final ConnectionEvent event) {
207 final PooledConnection pc = (PooledConnection) event.getSource();
208
209
210 if (!validatingSet.contains(pc)) {
211 final PooledConnectionAndInfo pci = pcMap.get(pc);
212 if (pci == null) {
213 throw new IllegalStateException(NO_KEY_MESSAGE);
214 }
215
216 try {
217 pool.returnObject(pci);
218 } catch (final Exception e) {
219 System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL");
220 pc.removeConnectionEventListener(this);
221 try {
222 doDestroyObject(pci);
223 } catch (final Exception e2) {
224 System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
225 e2.printStackTrace();
226 }
227 }
228 }
229 }
230
231
232
233
234 @Override
235 public void connectionErrorOccurred(final ConnectionEvent event) {
236 final PooledConnection pc = (PooledConnection) event.getSource();
237 if (null != event.getSQLException()) {
238 System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")");
239 }
240 pc.removeConnectionEventListener(this);
241
242 final PooledConnectionAndInfo pci = pcMap.get(pc);
243 if (pci == null) {
244 throw new IllegalStateException(NO_KEY_MESSAGE);
245 }
246 try {
247 pool.invalidateObject(pci);
248 } catch (final Exception e) {
249 System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
250 e.printStackTrace();
251 }
252 }
253
254
255
256
257 @Override
258 public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
259 doDestroyObject(p.getObject());
260 }
261
262 private void doDestroyObject(final PooledConnectionAndInfo pci) throws SQLException {
263 final PooledConnection pc = pci.getPooledConnection();
264 pc.removeConnectionEventListener(this);
265 pcMap.remove(pc);
266 pc.close();
267 }
268
269
270
271
272
273
274 char[] getPasswordCharArray() {
275 return userPassKey.getPasswordCharArray();
276 }
277
278
279
280
281
282
283 public ObjectPool<PooledConnectionAndInfo> getPool() {
284 return pool;
285 }
286
287
288
289
290
291
292 @Override
293 public void invalidate(final PooledConnection pc) throws SQLException {
294 final PooledConnectionAndInfo pci = pcMap.get(pc);
295 if (pci == null) {
296 throw new IllegalStateException(NO_KEY_MESSAGE);
297 }
298 try {
299 pool.invalidateObject(pci);
300 pool.close();
301 } catch (final Exception ex) {
302 throw new SQLException("Error invalidating connection", ex);
303 }
304 }
305
306 @Override
307 public synchronized PooledObject<PooledConnectionAndInfo> makeObject() throws SQLException {
308 PooledConnection pc = null;
309 if (userPassKey.getUserName() == null) {
310 pc = cpds.getPooledConnection();
311 } else {
312 pc = cpds.getPooledConnection(userPassKey.getUserName(), userPassKey.getPassword());
313 }
314 if (pc == null) {
315 throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
316 }
317
318
319 pc.addConnectionEventListener(this);
320 final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pc, userPassKey);
321 pcMap.put(pc, pci);
322 return new DefaultPooledObject<>(pci);
323 }
324
325 @Override
326 public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
327 validateLifetime(p);
328 }
329
330
331
332
333
334
335
336
337
338 public void setMaxConn(final Duration maxConnDuration) {
339 this.maxConnDuration = maxConnDuration;
340 }
341
342
343
344
345
346
347
348
349
350
351 @Deprecated
352 public void setMaxConnLifetime(final Duration maxConnDuration) {
353 this.maxConnDuration = maxConnDuration;
354 }
355
356
357
358
359
360
361
362
363
364 @Deprecated
365 public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
366 setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis));
367 }
368
369
370
371
372
373
374
375 public synchronized void setPassword(final char[] userPassword) {
376 this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
377 }
378
379
380
381
382
383
384
385 @Override
386 public synchronized void setPassword(final String userPassword) {
387 this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
388 }
389
390
391
392
393
394
395 public void setPool(final ObjectPool<PooledConnectionAndInfo> pool) {
396 this.pool = pool;
397 }
398
399
400
401
402 @Override
403 public synchronized String toString() {
404 final StringBuilder builder = new StringBuilder(super.toString());
405 builder.append("[cpds=");
406 builder.append(cpds);
407 builder.append(", validationQuery=");
408 builder.append(validationQuery);
409 builder.append(", validationQueryTimeoutDuration=");
410 builder.append(validationQueryTimeoutDuration);
411 builder.append(", rollbackAfterValidation=");
412 builder.append(rollbackAfterValidation);
413 builder.append(", pool=");
414 builder.append(pool);
415 builder.append(", maxConnDuration=");
416 builder.append(maxConnDuration);
417 builder.append(", validatingSet=");
418 builder.append(validatingSet);
419 builder.append(", pcMap=");
420 builder.append(pcMap);
421 builder.append("]");
422 return builder.toString();
423 }
424
425 private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
426 Utils.validateLifetime(p, maxConnDuration);
427 }
428
429 @Override
430 public boolean validateObject(final PooledObject<PooledConnectionAndInfo> p) {
431 try {
432 validateLifetime(p);
433 } catch (final Exception e) {
434 return false;
435 }
436 boolean valid = false;
437 final PooledConnection pconn = p.getObject().getPooledConnection();
438 Connection conn = null;
439 validatingSet.add(pconn);
440 if (null == validationQuery) {
441 Duration timeoutDuration = validationQueryTimeoutDuration;
442 if (timeoutDuration.isNegative()) {
443 timeoutDuration = Duration.ZERO;
444 }
445 try {
446 conn = pconn.getConnection();
447 valid = conn.isValid((int) timeoutDuration.getSeconds());
448 } catch (final SQLException e) {
449 valid = false;
450 } finally {
451 Utils.closeQuietly((AutoCloseable) conn);
452 validatingSet.remove(pconn);
453 }
454 } else {
455 Statement stmt = null;
456 ResultSet rset = null;
457
458
459
460
461 validatingSet.add(pconn);
462 try {
463 conn = pconn.getConnection();
464 stmt = conn.createStatement();
465 rset = stmt.executeQuery(validationQuery);
466 valid = rset.next();
467 if (rollbackAfterValidation) {
468 conn.rollback();
469 }
470 } catch (final Exception e) {
471 valid = false;
472 } finally {
473 Utils.closeQuietly((AutoCloseable) rset);
474 Utils.closeQuietly((AutoCloseable) stmt);
475 Utils.closeQuietly((AutoCloseable) conn);
476 validatingSet.remove(pconn);
477 }
478 }
479 return valid;
480 }
481 }