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.KeyedObjectPool;
36 import org.apache.commons.pool2.KeyedPooledObjectFactory;
37 import org.apache.commons.pool2.PooledObject;
38 import org.apache.commons.pool2.impl.DefaultPooledObject;
39
40
41
42
43
44
45
46 final class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey, PooledConnectionAndInfo>,
47 ConnectionEventListener, PooledConnectionManager {
48
49 private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but "
50 + "I have no record of the underlying PooledConnection.";
51
52 private final ConnectionPoolDataSource cpds;
53 private final String validationQuery;
54 private final Duration validationQueryTimeoutDuration;
55 private final boolean rollbackAfterValidation;
56 private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
57 private Duration maxConnLifetime = Duration.ofMillis(-1);
58
59
60
61
62 private final Set<PooledConnection> validatingSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
63
64
65
66
67 private final Map<PooledConnection, PooledConnectionAndInfo> pcMap = new ConcurrentHashMap<>();
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
85 final Duration validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
86 this.cpds = cpds;
87 this.validationQuery = validationQuery;
88 this.validationQueryTimeoutDuration = validationQueryTimeoutSeconds;
89 this.rollbackAfterValidation = rollbackAfterValidation;
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 @Deprecated
108 public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
109 final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
110 this(cpds, validationQuery, Duration.ofSeconds(validationQueryTimeoutSeconds), rollbackAfterValidation);
111 }
112
113 @Override
114 public void activateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
115 validateLifetime(p);
116 }
117
118
119
120
121
122 @Override
123 public void closePool(final String userName) throws SQLException {
124 try {
125 pool.clear(new UserPassKey(userName));
126 } catch (final Exception ex) {
127 throw new SQLException("Error closing connection pool", ex);
128 }
129 }
130
131
132
133
134
135
136 @Override
137 public void connectionClosed(final ConnectionEvent event) {
138 final PooledConnection pc = (PooledConnection) event.getSource();
139
140
141
142 if (!validatingSet.contains(pc)) {
143 final PooledConnectionAndInfo pci = pcMap.get(pc);
144 if (pci == null) {
145 throw new IllegalStateException(NO_KEY_MESSAGE);
146 }
147 try {
148 pool.returnObject(pci.getUserPassKey(), pci);
149 } catch (final Exception e) {
150 System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL");
151 pc.removeConnectionEventListener(this);
152 try {
153 pool.invalidateObject(pci.getUserPassKey(), pci);
154 } catch (final Exception e3) {
155 System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
156 e3.printStackTrace();
157 }
158 }
159 }
160 }
161
162
163
164
165 @Override
166 public void connectionErrorOccurred(final ConnectionEvent event) {
167 final PooledConnection pc = (PooledConnection) event.getSource();
168 if (null != event.getSQLException()) {
169 System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")");
170 }
171 pc.removeConnectionEventListener(this);
172
173 final PooledConnectionAndInfo info = pcMap.get(pc);
174 if (info == null) {
175 throw new IllegalStateException(NO_KEY_MESSAGE);
176 }
177 try {
178 pool.invalidateObject(info.getUserPassKey(), info);
179 } catch (final Exception e) {
180 System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
181 e.printStackTrace();
182 }
183 }
184
185
186
187
188 @Override
189 public void destroyObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
190 final PooledConnection pooledConnection = p.getObject().getPooledConnection();
191 pooledConnection.removeConnectionEventListener(this);
192 pcMap.remove(pooledConnection);
193 pooledConnection.close();
194 }
195
196
197
198
199
200
201 public KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> getPool() {
202 return pool;
203 }
204
205
206
207
208
209
210
211 @Override
212 public void invalidate(final PooledConnection pc) throws SQLException {
213 final PooledConnectionAndInfo info = pcMap.get(pc);
214 if (info == null) {
215 throw new IllegalStateException(NO_KEY_MESSAGE);
216 }
217 final UserPassKey key = info.getUserPassKey();
218 try {
219 pool.invalidateObject(key, info);
220 pool.clear(key);
221 } catch (final Exception ex) {
222 throw new SQLException("Error invalidating connection", ex);
223 }
224 }
225
226
227
228
229
230
231
232
233
234
235 @Override
236 public synchronized PooledObject<PooledConnectionAndInfo> makeObject(final UserPassKey userPassKey) throws SQLException {
237 PooledConnection pooledConnection = null;
238 final String userName = userPassKey.getUserName();
239 final String password = userPassKey.getPassword();
240 if (userName == null) {
241 pooledConnection = cpds.getPooledConnection();
242 } else {
243 pooledConnection = cpds.getPooledConnection(userName, password);
244 }
245
246 if (pooledConnection == null) {
247 throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
248 }
249
250
251
252 pooledConnection.addConnectionEventListener(this);
253 final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pooledConnection, userPassKey);
254 pcMap.put(pooledConnection, pci);
255
256 return new DefaultPooledObject<>(pci);
257 }
258
259 @Override
260 public void passivateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
261 validateLifetime(p);
262 }
263
264
265
266
267
268
269
270
271
272 public void setMaxConn(final Duration maxConnLifetimeMillis) {
273 this.maxConnLifetime = maxConnLifetimeMillis;
274 }
275
276
277
278
279
280
281
282
283
284
285 @Deprecated
286 public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) {
287 this.maxConnLifetime = maxConnLifetimeMillis;
288 }
289
290
291
292
293
294
295
296
297
298 @Deprecated
299 public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
300 setMaxConn(Duration.ofMillis(maxConnLifetimeMillis));
301 }
302
303
304
305
306 @Override
307 public void setPassword(final String password) {
308
309 }
310
311 public void setPool(final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool) {
312 this.pool = pool;
313 }
314
315 private void validateLifetime(final PooledObject<PooledConnectionAndInfo> pooledObject) throws SQLException {
316 Utils.validateLifetime(pooledObject, maxConnLifetime);
317 }
318
319
320
321
322
323
324
325
326
327
328 @Override
329 public boolean validateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> pooledObject) {
330 try {
331 validateLifetime(pooledObject);
332 } catch (final Exception e) {
333 return false;
334 }
335 boolean valid = false;
336 final PooledConnection pooledConn = pooledObject.getObject().getPooledConnection();
337 Connection conn = null;
338 validatingSet.add(pooledConn);
339 if (null == validationQuery) {
340 Duration timeoutDuration = validationQueryTimeoutDuration;
341 if (timeoutDuration.isNegative()) {
342 timeoutDuration = Duration.ZERO;
343 }
344 try {
345 conn = pooledConn.getConnection();
346 valid = conn.isValid((int) timeoutDuration.getSeconds());
347 } catch (final SQLException e) {
348 valid = false;
349 } finally {
350 Utils.closeQuietly((AutoCloseable) conn);
351 validatingSet.remove(pooledConn);
352 }
353 } else {
354 Statement stmt = null;
355 ResultSet rset = null;
356
357
358
359
360 validatingSet.add(pooledConn);
361 try {
362 conn = pooledConn.getConnection();
363 stmt = conn.createStatement();
364 rset = stmt.executeQuery(validationQuery);
365 valid = rset.next();
366 if (rollbackAfterValidation) {
367 conn.rollback();
368 }
369 } catch (final Exception e) {
370 valid = false;
371 } finally {
372 Utils.closeQuietly((AutoCloseable) rset);
373 Utils.closeQuietly((AutoCloseable) stmt);
374 Utils.closeQuietly((AutoCloseable) conn);
375 validatingSet.remove(pooledConn);
376 }
377 }
378 return valid;
379 }
380 }