1 package org.apache.torque.pool;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 import java.sql.SQLException;
58 import java.util.HashMap;
59 import java.util.Map;
60 import java.util.Stack;
61
62 import javax.sql.ConnectionEvent;
63 import javax.sql.ConnectionEventListener;
64 import javax.sql.ConnectionPoolDataSource;
65 import javax.sql.PooledConnection;
66
67 import org.apache.commons.logging.Log;
68 import org.apache.commons.logging.LogFactory;
69
70 /***
71 * This class implements a simple connection pooling scheme.
72 *
73 * @author <a href="mailto:csterg@aias.gr">Costas Stergiou</a>
74 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
75 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
76 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
77 * @author <a href="mailto:dlr@collab.net">Daniel L. Rall</a>
78 * @author <a href="mailto:paul@evolventtech.com">Paul O'Leary</a>
79 * @author <a href="mailto:magnus@handtolvur.is">Magn?s ??r Torfason</a>
80 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
81 * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
82 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
83 * @version $Id: ConnectionPool.java,v 1.27 2003/08/18 21:48:11 mpoeschl Exp $
84 * @deprecated as of version 3.1
85 */
86 class ConnectionPool implements ConnectionEventListener
87 {
88
89 /*** Default maximum Number of connections from this pool: One */
90 public static final int DEFAULT_MAX_CONNECTIONS = 1;
91
92 /*** Default Expiry Time for a pool: 1 hour */
93 public static final int DEFAULT_EXPIRY_TIME = 60 * 60 * 1000;
94
95 /*** Default Connect Wait Timeout: 10 Seconds */
96 public static final int DEFAULT_CONNECTION_WAIT_TIMEOUT = 10 * 1000;
97
98 /*** Pool containing database connections. */
99 private Stack pool;
100
101 /*** The url for this pool. */
102 private String url;
103
104 /*** The user name for this pool. */
105 private String username;
106
107 /*** The password for this pool. */
108 private String password;
109
110 /*** The current number of database connections that have been created. */
111 private int totalConnections;
112
113 /*** The maximum number of database connections that can be created. */
114 private int maxConnections = DEFAULT_MAX_CONNECTIONS;
115
116 /*** The amount of time in milliseconds that a connection will be pooled. */
117 private long expiryTime = DEFAULT_EXPIRY_TIME;
118
119 /***
120 * Counter that keeps track of the number of threads that are in
121 * the wait state, waiting to aquire a connection.
122 */
123 private int waitCount = 0;
124
125 /*** The logging logger. */
126 private static Log log = LogFactory.getLog(ConnectionPool.class);
127
128 /*** Interval (in seconds) that the monitor thread reports the pool state */
129 private int logInterval = 0;
130
131 /*** Monitor thread reporting the pool state */
132 private Monitor monitor;
133
134 /***
135 * Amount of time a thread asking the pool for a cached connection will
136 * wait before timing out and throwing an error.
137 */
138 private long connectionWaitTimeout = DEFAULT_CONNECTION_WAIT_TIMEOUT;
139
140 /*** The ConnectionPoolDataSource */
141 private ConnectionPoolDataSource cpds;
142
143 /***
144 * Keep track of when connections were created. Keyed by a
145 * PooledConnection and value is a java.util.Date
146 */
147 private Map timeStamps;
148
149 /***
150 * Creates a <code>ConnectionPool</code> with the default
151 * attributes.
152 *
153 * @param cpds The datasource
154 * @param username The user name for this pool.
155 * @param password The password for this pool.
156 * @param maxConnections max number of connections
157 * @param expiryTime connection expiry time
158 * @param connectionWaitTimeout timeout
159 * @param logInterval log interval
160 */
161 ConnectionPool(ConnectionPoolDataSource cpds, String username,
162 String password, int maxConnections, int expiryTime,
163 int connectionWaitTimeout, int logInterval)
164 {
165 totalConnections = 0;
166 pool = new Stack();
167 timeStamps = new HashMap();
168
169 this.cpds = cpds;
170 this.username = username;
171 this.password = password;
172
173 this.maxConnections =
174 (maxConnections > 0) ? maxConnections : DEFAULT_MAX_CONNECTIONS;
175
176 this.expiryTime =
177 ((expiryTime > 0) ? expiryTime * 1000 : DEFAULT_EXPIRY_TIME);
178
179 this.connectionWaitTimeout =
180 ((connectionWaitTimeout > 0)
181 ? connectionWaitTimeout * 1000
182 : DEFAULT_CONNECTION_WAIT_TIMEOUT);
183
184 this.logInterval = 1000 * logInterval;
185
186 if (logInterval > 0)
187 {
188 log.debug("Starting Pool Monitor Thread with Log Interval "
189 + logInterval + " Milliseconds");
190
191
192 monitor = new Monitor();
193
194
195
196
197
198 monitor.setDaemon(true);
199 monitor.start();
200 }
201 }
202
203 /***
204 * Returns a connection that maintains a link to the pool it came from.
205 *
206 * @param username The name of the database user.
207 * @param password The password of the database user.
208 * @return A database connection.
209 * @exception SQLException if there is aproblem with the db connection
210 */
211 final synchronized PooledConnection getConnection(String username,
212 String password)
213 throws SQLException
214 {
215 if (username != this.username || password != this.password)
216 {
217 throw new SQLException("Username and password are invalid.");
218 }
219
220 PooledConnection pcon = null;
221 if (pool.empty() && totalConnections < maxConnections)
222 {
223 pcon = getNewConnection();
224 }
225 else
226 {
227 try
228 {
229 pcon = getInternalPooledConnection();
230 }
231 catch (Exception e)
232 {
233 throw new SQLException(e.getMessage());
234 }
235 }
236 return pcon;
237 }
238
239 /***
240 * Returns a fresh connection to the database. The database type
241 * is specified by <code>driver</code>, and its connection
242 * information by <code>url</code>, <code>username</code>, and
243 * <code>password</code>.
244 *
245 * @return A database connection.
246 * @exception SQLException if there is aproblem with the db connection
247 */
248 private PooledConnection getNewConnection()
249 throws SQLException
250 {
251 PooledConnection pc = null;
252 if (username == null)
253 {
254 pc = cpds.getPooledConnection();
255 }
256 else
257 {
258 pc = cpds.getPooledConnection(username, password);
259 }
260 pc.addConnectionEventListener(this);
261
262
263
264
265
266
267
268 long currentTime = System.currentTimeMillis();
269
270 double ratio = new Long(maxConnections - totalConnections).doubleValue()
271 / maxConnections;
272
273 long ratioTime = new Double(currentTime - (expiryTime * ratio) / 4)
274 .longValue();
275
276 ratioTime = (expiryTime < 0) ? currentTime : ratioTime;
277
278 timeStamps.put(pc, new Long(ratioTime));
279 totalConnections++;
280 return pc;
281 }
282
283 /***
284 * Gets a pooled database connection.
285 *
286 * @return A database connection.
287 * @exception ConnectionWaitTimeoutException Wait time exceeded.
288 * @exception Exception No pooled connections.
289 */
290 private synchronized PooledConnection getInternalPooledConnection()
291 throws ConnectionWaitTimeoutException, Exception
292 {
293
294
295 if (waitCount > 0 || pool.empty())
296 {
297
298
299
300 try
301 {
302 waitCount++;
303 wait(connectionWaitTimeout);
304 }
305 catch (InterruptedException ignored)
306 {
307
308 }
309 finally
310 {
311 waitCount--;
312 }
313
314
315 if (pool.empty())
316 {
317
318
319 throw new ConnectionWaitTimeoutException(url);
320 }
321 }
322 return popConnection();
323 }
324 /***
325 * Helper function that attempts to pop a connection off the pool's stack,
326 * handling the case where the popped connection has become invalid by
327 * creating a new connection.
328 *
329 * @return An existing or new database connection.
330 * @throws Exception if the pool is empty
331 */
332 private PooledConnection popConnection()
333 throws Exception
334 {
335 while (!pool.empty())
336 {
337 PooledConnection con = (PooledConnection) pool.pop();
338
339
340
341 if (isValid(con))
342 {
343 return con;
344 }
345 else
346 {
347
348 con.close();
349 totalConnections--;
350
351
352
353
354
355 if (pool.empty())
356 {
357 return getNewConnection();
358 }
359 }
360 }
361
362
363
364
365 throw new Exception("Assertion failure: Attempted to pop "
366 + "connection from empty pool!");
367 }
368
369 /***
370 * Helper method which determines whether a connection has expired.
371 *
372 * @param pc The connection to test.
373 * @return True if the connection is expired, false otherwise.
374 */
375 private boolean isExpired(PooledConnection pc)
376 {
377
378
379
380 long birth = ((Long) timeStamps.get(pc)).longValue();
381 long age = System.currentTimeMillis() - birth;
382
383 boolean dead = (expiryTime > 0)
384 ? age > expiryTime
385 : age > DEFAULT_EXPIRY_TIME;
386
387 return dead;
388 }
389
390 /***
391 * Determines if a connection is still valid.
392 *
393 * @param connection The connection to test.
394 * @return True if the connection is valid, false otherwise.
395 */
396 private boolean isValid(PooledConnection connection)
397 {
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415 return !isExpired(connection);
416
417
418
419
420
421
422
423 }
424
425
426 /***
427 * Close any open connections when this object is garbage collected.
428 *
429 * @exception Throwable Anything might happen...
430 */
431 protected void finalize()
432 throws Throwable
433 {
434 shutdown();
435 }
436
437 /***
438 * Close all connections to the database,
439 */
440 void shutdown()
441 {
442 if (pool != null)
443 {
444 while (!pool.isEmpty())
445 {
446 try
447 {
448 ((PooledConnection) pool.pop()).close();
449 }
450 catch (SQLException ignore)
451 {
452 }
453 finally
454 {
455 totalConnections--;
456 }
457 }
458 }
459 monitor.shutdown();
460 }
461
462 /***
463 * Returns the Total connections in the pool
464 *
465 * @return total connections in the pool
466 */
467 int getTotalCount()
468 {
469 return totalConnections;
470 }
471
472 /***
473 * Returns the available connections in the pool
474 *
475 * @return number of available connections in the pool
476 */
477 int getNbrAvailable()
478 {
479 return pool.size();
480 }
481
482 /***
483 * Returns the checked out connections in the pool
484 *
485 * @return number of checked out connections in the pool
486 */
487 int getNbrCheckedOut()
488 {
489 return (totalConnections - pool.size());
490 }
491
492 /***
493 * Decreases the count of connections in the pool
494 * and also calls <code>notify()</code>.
495 */
496 void decrementConnections()
497 {
498 totalConnections--;
499 notify();
500 }
501
502 /***
503 * Get the name of the pool
504 *
505 * @return the name of the pool
506 */
507 String getPoolName()
508 {
509 return toString();
510 }
511
512
513
514
515
516 /***
517 * This will be called if the Connection returned by the getConnection
518 * method came from a PooledConnection, and the user calls the close()
519 * method of this connection object. What we need to do here is to
520 * release this PooledConnection from our pool...
521 *
522 * @param event the connection event
523 */
524 public void connectionClosed(ConnectionEvent event)
525 {
526 releaseConnection((PooledConnection) event.getSource());
527 }
528
529 /***
530 * If a fatal error occurs, close the underlying physical connection so as
531 * not to be returned in the future
532 *
533 * @param event the connection event
534 */
535 public void connectionErrorOccurred(ConnectionEvent event)
536 {
537 try
538 {
539 System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR");
540
541
542 ((PooledConnection) event.getSource())
543 .removeConnectionEventListener(this);
544 }
545 catch (Exception ignore)
546 {
547
548 }
549
550 try
551 {
552 closePooledConnection((PooledConnection) event.getSource());
553 }
554 catch (Exception ignore)
555 {
556
557 }
558 }
559
560 /***
561 * This method returns a connection to the pool, and <b>must</b>
562 * be called by the requestor when finished with the connection.
563 *
564 * @param pcon The database connection to release.
565 */
566 private synchronized void releaseConnection(PooledConnection pcon)
567 {
568 if (isValid(pcon))
569 {
570 pool.push(pcon);
571 notify();
572 }
573 else
574 {
575 closePooledConnection(pcon);
576 }
577 }
578
579 /***
580 *
581 * @param pcon The database connection to close.
582 */
583 private void closePooledConnection(PooledConnection pcon)
584 {
585 try
586 {
587 pcon.close();
588 timeStamps.remove(pcon);
589 }
590 catch (Exception e)
591 {
592 log.error("Error occurred trying to close a PooledConnection.", e);
593 }
594 finally
595 {
596 decrementConnections();
597 }
598 }
599
600
601
602 /***
603 * This inner class monitors the <code>PoolBrokerService</code>.
604 *
605 * This class is capable of logging the number of connections available in
606 * the pool periodically. This can prove useful if you application
607 * frozes after certain amount of time/requests and you suspect
608 * that you have connection leakage problem.
609 *
610 * Set the <code>logInterval</code> property of your pool definition
611 * to the number of seconds you want to elapse between loging the number of
612 * connections.
613 */
614 protected class Monitor extends Thread
615 {
616 /*** true if the monot is running */
617 private boolean isRun = true;
618
619 /***
620 * run method for the monitor thread
621 */
622 public void run()
623 {
624 StringBuffer buf = new StringBuffer();
625 while (logInterval > 0 && isRun)
626 {
627 buf.setLength(0);
628
629 buf.append(getPoolName());
630 buf.append(" avail: ").append(getNbrAvailable());
631 buf.append(" in use: ").append(getNbrCheckedOut());
632 buf.append(" total: ").append(getTotalCount());
633 log.info(buf.toString());
634
635
636 try
637 {
638 Thread.sleep(logInterval);
639 }
640 catch (InterruptedException ignored)
641 {
642 }
643 }
644 }
645
646 /***
647 * Shut down the monitor
648 */
649 public void shutdown()
650 {
651 isRun = false;
652 }
653 }
654 }