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