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