001: /*
002: DBPool - JDBC Connection Pool Manager
003: Copyright (c) Giles Winstanley
004: */
005: package snaq.util;
006:
007: import java.io.*;
008: import java.text.*;
009: import java.util.*;
010:
011: /**
012: * Base class for a pool system implementation.
013: * This class provides all the base functionality required and can be easily
014: * extended to provide pooling support for many different types of object.
015: * <p>New objects are retrieved on demand according to specified limits,
016: * and the pool can also ensure an object's validity before returning it.
017: * The limits which can be set for a pool include the number of items to be
018: * held in the pool, and the maximum number to ever be created.
019: * <p>The pool will try to maintain <tt>poolSize</tt> items open and ready
020: * for use (unless that number has not yet been created), but if expiry is
021: * enabled this number will reduce if the items are not used frequently.
022: * If <tt>maxSize</tt> is specified then the pool will never create more
023: * than this number of items, and another can only be obtained from the pool
024: * if it is handed back by another client.
025: * <p><tt>ObjectPool</tt> should be sub-classed to override
026: * the following methods:</p>
027: * <pre>
028: * protected Reusable create() throws Exception
029: * protected boolean isValid(final Reusable o)
030: * protected void destroy(final Reusable o)
031: * </pre>
032: * <p>It is recommended that the sub-class implements methods for obtaining
033: * and returning items within the pool, casting the objects returned by this
034: * class as required to the appropriate class type.
035: * <p>ObjectPool also support asynchronous destruction of items, which can be
036: * useful in circumstances where destruction of items held can take a long
037: * time which would delay the <tt>checkIn</tt> method. This also applies
038: * to the release of the pool after it's final use, which should always be
039: * done using either <tt>release</tt> or <tt>releaseAsync</tt>.
040: * @author Giles Winstanley
041: */
042: public abstract class ObjectPool extends LogUtil {
043: // Pool access method
044: private static final int ACCESS_FIFO = 1;
045: private static final int ACCESS_LIFO = 2;
046: private static final int ACCESS_RANDOM = 3;
047: private int accessMethod = ACCESS_LIFO;
048: // Other variables
049: private String name;
050: private List free, used;
051: private int poolSize, maxSize;
052: private long expiryTime;
053: private long requests, hits;
054: private boolean released = false;
055: private boolean asyncDestroy = false;
056: private DateFormat dateFormat;
057: private Cleaner cleaner;
058: private InitThread initer;
059: private static int cleanerCount = 0;
060: private List listeners = new ArrayList();
061:
062: /**
063: * Creates new object pool.
064: * @param name pool name
065: * @param poolSize maximum number of pooled objects, or 0 for no limit
066: * @param maxSize maximum number of possible objects, or 0 for no limit
067: * @param expiryTime expiry time (milliseconds) for pooled object, or 0 for no expiry
068: */
069: protected ObjectPool(String name, int poolSize, int maxSize,
070: long expiryTime) {
071: Class type = getPoolClass();
072: if (!List.class.isAssignableFrom(type))
073: throw new RuntimeException(
074: "Invalid pool class type specified: "
075: + type.getName()
076: + " (must implement java.util.List)");
077: try {
078: free = (List) type.newInstance();
079: used = (List) type.newInstance();
080: } catch (Exception e) {
081: throw new RuntimeException(
082: "Unable to instantiate pool class type: "
083: + type.getName());
084: }
085: this .name = name;
086: this .setParameters(poolSize, maxSize, expiryTime);
087: }
088:
089: /**
090: * Creates new object pool.
091: * @param name pool name
092: * @param poolSize maximum number of pooled objects, or 0 for no limit
093: * @param maxSize maximum number of possible objects, or 0 for no limit
094: * @param expiryTime expiry time (milliseconds) for pooled object, or 0 for no expiry
095: */
096: protected ObjectPool(String name, int poolSize, int maxSize,
097: int expiryTime) {
098: this (name, poolSize, maxSize, (long) expiryTime);
099: }
100:
101: /**
102: * Initializes the given number of items in the pool.
103: * This spawns a new thread to create them in the background.
104: */
105: public final void init(int num) {
106: if (num == 0)
107: return;
108: if (num > 0 && num <= getMaxSize()) {
109: if (initer != null) {
110: initer.halt();
111: try {
112: initer.join();
113: } catch (InterruptedException ie) {
114: }
115: }
116: initer = new InitThread(num);
117: initer.start();
118: } else
119: throw new IllegalArgumentException(
120: "Invalid number of items specified for initialization");
121: }
122:
123: /**
124: * Checks out an item from the pool. If no free item
125: * is available, a new item is created unless the maximum
126: * number of items has been reached. If a free item
127: * is not valid it is removed from the pool and another
128: * is retrieved.
129: * @return item from the pool, or null if nothing available
130: * @exception Exception if there is an error creating a new object
131: */
132: protected final synchronized Reusable checkOut() throws Exception {
133: if (released)
134: throw new RuntimeException("Pool no longer valid for use");
135: int oldTotalConns = used.size() + free.size();
136:
137: TimeWrapper tw = null;
138: Reusable o = null;
139: if (free.size() > 0) {
140: // Get an object from the free list
141: switch (accessMethod) {
142: case ACCESS_FIFO:
143: tw = (TimeWrapper) free.remove(0);
144: break;
145: case ACCESS_RANDOM:
146: tw = (TimeWrapper) free
147: .remove((int) (free.size() * Math.random()));
148: break;
149: case ACCESS_LIFO:
150: default:
151: tw = (TimeWrapper) free.remove(free.size() - 1);
152: }
153: o = (Reusable) tw.getObject();
154: boolean valid = isValid(o);
155: while (!valid && free.size() > 0) {
156: destroyObject(o);
157: log("Removed invalid item from pool");
158: tw = (TimeWrapper) free.remove(0);
159: o = (Reusable) tw.getObject();
160: valid = isValid(o);
161: }
162: if (free.size() == 0 && !valid)
163: o = null;
164: }
165: boolean hit = (o != null);
166:
167: // If no free items and can create more...create new item
168: if (o == null) {
169: if (maxSize > 0 && used.size() == maxSize)
170: fireMaxSizeLimitErrorEvent();
171: else if (used.size() < maxSize || maxSize == 0) {
172: o = create();
173: if (!isValid(o))
174: throw new RuntimeException(
175: "Unable to create a valid connection");
176: }
177: }
178:
179: // If a connection has been obtained/created, add it to used items collection
180: if (o != null) {
181: used.add(o);
182: requests++;
183: if (hit)
184: hits++;
185: firePoolCheckOutEvent();
186: // Check for limit reaching so events can be fired.
187: // (Events only fired on increase of connection numbers).
188: int totalConns = used.size() + free.size();
189: if (totalConns == poolSize && totalConns > oldTotalConns)
190: fireMaxPoolLimitReachedEvent();
191: else if (totalConns == poolSize + 1
192: && totalConns > oldTotalConns)
193: fireMaxPoolLimitExceededEvent();
194: if (totalConns == maxSize && totalConns > oldTotalConns)
195: fireMaxSizeLimitReachedEvent();
196: }
197: if (debug) {
198: String ratio = used.size() + "/"
199: + (used.size() + free.size());
200: String hitRate = " (HitRate=" + getHitRate() + "%)";
201: log("Checkout - " + ratio + hitRate
202: + (o == null ? " - null returned" : ""));
203: }
204: return o;
205: }
206:
207: /**
208: * Checks out an item from the pool.
209: * If there is no pooled item available and the maximum number
210: * possible has not been reached, another is created.
211: * If a free item is detected as being invalid it is removed
212: * from the pool and the another is retrieved.
213: * If an item is not available and the maximum number possible
214: * has been reached, the method waits for the timeout period
215: * for one to become available by being checked in.
216: * @param timeout timeout value in milliseconds
217: * @return item from the pool, or null if nothing available within timeout period
218: * @exception Exception if there is an error creating a new object
219: */
220: protected final synchronized Reusable checkOut(long timeout)
221: throws Exception {
222: long time = System.currentTimeMillis();
223: Reusable o = null;
224: o = checkOut();
225: while (o == null
226: && (System.currentTimeMillis() - time < timeout)) {
227: try {
228: if (debug)
229: log("No pooled items spare...waiting for up to "
230: + timeout + "ms");
231: wait(timeout);
232: o = checkOut();
233: } catch (InterruptedException e) {
234: log(e, "Connection checkout interrupted");
235: }
236: }
237: return o;
238: }
239:
240: /**
241: * Checks an object into the pool, and notify other
242: * threads that may be waiting for one.
243: * @param o object to check in
244: */
245: protected final void checkIn(Reusable o) {
246: if (o == null) {
247: log("Attempt to return null item");
248: return;
249: }
250:
251: synchronized (this ) {
252: firePoolCheckInEvent();
253:
254: // Check if item is from this pool
255: if (!used.remove(o)) {
256: log("Attempt to return item not belonging to pool");
257: throw new RuntimeException(
258: "Attempt to return item not belonging to pool "
259: + name);
260: }
261:
262: // If pool is full destroy object, else place in pool
263: boolean kill = maxSize > 0
264: && (free.size() + used.size() >= poolSize);
265: kill = kill || (maxSize == 0 && free.size() >= poolSize);
266: if (kill) {
267: destroyObject(o);
268: if (debug)
269: log("Checkin* - " + used.size() + "/"
270: + (used.size() + free.size()));
271: } else {
272: try {
273: // Recycle object for next use
274: o.recycle();
275: // Add object to free list
276: free.add(new TimeWrapper(null, o, expiryTime));
277: if (debug)
278: log("Checkin - " + used.size() + "/"
279: + (used.size() + free.size()));
280: notifyAll();
281: } catch (Exception e) {
282: // If unable to recycle object, destroy it
283: destroyObject(o);
284: log(e, "Unable to recycle item - destroyed");
285: }
286: }
287: }
288: }
289:
290: /**
291: * Releases all items from the pool, and shuts the pool down.
292: * If any items are still checked-out, this method waits until all items have
293: * been checked-in before returning.
294: */
295: public final void release() {
296: release(false);
297: }
298:
299: /**
300: * Releases all items from the pool, and shuts the pool down.
301: * This method returns immediately; a background thread is created to perform the release.
302: */
303: public final synchronized void releaseAsync() {
304: releaseAsync(false);
305: }
306:
307: /**
308: * Forcibly releases all items from the pool, and shuts the pool down.
309: * If any items are still checked-out, this method forcibly destroys them
310: * and then returns.
311: */
312: public final void releaseForcibly() {
313: release(true);
314: }
315:
316: /**
317: * Releases all items from the pool, and shuts the pool down.
318: * @param forced whether to forcibly destroy items, or let them be checked-in
319: */
320: private final void release(boolean forced) {
321: // Set released flag to prevent check-out of new items
322: if (released)
323: return;
324: released = true;
325: // Destroy cleaner thread
326: if (cleaner != null) {
327: cleaner.halt();
328: try {
329: cleaner.join();
330: } catch (InterruptedException ie) {
331: log(ie,
332: "Interrupted during halting of old cleaner thread");
333: }
334: cleaner = null;
335: }
336:
337: synchronized (this ) {
338: int rel = 0, failed = 0;
339: TimeWrapper tw = null;
340: Reusable o = null;
341: // Destroy all items still in use
342: if (forced) {
343: for (Iterator iter = used.iterator(); iter.hasNext();) {
344: o = (Reusable) iter.next();
345: try {
346: destroy(o);
347: rel++;
348: } catch (Exception ex) {
349: failed++;
350: log(ex, "Unable to release item in pool");
351: }
352: }
353: used.clear();
354: } else {
355: if (debug && used.size() > 0)
356: log("Waiting for used items to be checked-in...");
357: while (used.size() > 0) {
358: try {
359: wait();
360: } catch (InterruptedException e) {
361: }
362: }
363: }
364:
365: // Destroy all currently free items
366: for (Iterator iter = free.iterator(); iter.hasNext();) {
367: tw = (TimeWrapper) iter.next();
368: o = (Reusable) tw.getObject();
369: try {
370: destroy(o);
371: rel++;
372: } catch (Exception ex) {
373: failed++;
374: log(ex, "Unable to release item in pool");
375: }
376: }
377: free.clear();
378:
379: // Destroy log reference
380: if (debug) {
381: String s = "Released " + rel
382: + (rel > 1 ? " items" : " item");
383: if (failed > 0)
384: s += " (failed to release " + failed
385: + (failed > 1 ? " items)" : " item)");
386: log(s);
387: }
388: firePoolReleasedEvent();
389: listeners.clear();
390: super .close();
391: }
392: }
393:
394: /**
395: * Releases all items from the pool, and shuts the pool down.
396: * This method returns immediately; a background thread is created to perform the release.
397: */
398: private final void releaseAsync(final boolean forced) {
399: Thread t = new Thread(new Runnable() {
400: public void run() {
401: release(forced);
402: }
403: });
404: t.start();
405: }
406:
407: /**
408: * Object creation method.
409: * This method is called when a new item needs to be created following a call
410: * to one of the check-out methods.
411: * @exception Exception if unable to create the item
412: */
413: protected abstract Reusable create() throws Exception;
414:
415: /**
416: * Object validation method.
417: * This method is called when checking-out an item to see if it is valid for use.
418: * When overridden by the sub-class it is recommended that this method perform
419: * suitable checks to ensure the object can be used without problems.
420: */
421: protected abstract boolean isValid(final Reusable o);
422:
423: /**
424: * Object destruction method.
425: * This method is called when an object needs to be destroyed due to pool
426: * pruning/cleaning, or final release of the pool.
427: */
428: protected abstract void destroy(final Reusable o);
429:
430: /**
431: * Destroys the given object (asynchronously if necessary).
432: */
433: private final void destroyObject(final Reusable o) {
434: if (o == null)
435: return;
436: if (asyncDestroy) {
437: Thread t = new Thread(new Runnable() {
438: public void run() {
439: destroy(o);
440: }
441: });
442: t.start();
443: } else
444: destroy(o);
445: }
446:
447: /**
448: * Determines whether to perform asynchronous object destruction.
449: * If set to true then each time an object is destroyed (invalid object
450: * during pool operation, or when the pool is finally released) the operation
451: * is done in a separate thread, allowing the method to return immediately.
452: * This can be useful when calling the destroy method on an object takes a
453: * long time to complete.
454: */
455: public final void setAsyncDestroy(boolean b) {
456: asyncDestroy = b;
457: }
458:
459: /**
460: * Returns whether asynchronous object destruction is enabled.
461: * (Default: false)
462: */
463: public final boolean isAsyncDestroy() {
464: return asyncDestroy;
465: }
466:
467: /**
468: * Writes a message to the log.
469: */
470: public void log(String logEntry) {
471: log(name + ": ", logEntry);
472: }
473:
474: /**
475: * Writes a message with an Exception to the log file.
476: */
477: public void log(Throwable e, String logEntry) {
478: log(e, name + ": ", logEntry);
479: }
480:
481: /**
482: * Returns the pool name.
483: */
484: public final String getName() {
485: return this .name;
486: }
487:
488: /**
489: * Returns the maximum size of the pool.
490: */
491: public final int getPoolSize() {
492: return poolSize;
493: }
494:
495: /**
496: * Returns the maximum number of items that can be created.
497: */
498: public final int getMaxSize() {
499: return maxSize;
500: }
501:
502: /**
503: * Returns the expiry time for unused items in the pool.
504: */
505: public final long getExpiryTime() {
506: return expiryTime;
507: }
508:
509: /**
510: * Sets the pooling parameters.
511: * Any items currently in the pool will remain, subject to the new parameters.
512: * (The hit rate counters are reset when this method is called.)
513: * @param poolSize number of items to be keep in pool
514: * @param maxSize maximum number of items to be created
515: * @param expiryTime expiry time for unused items (milliseconds) (0 = no expiry)
516: */
517: public final void setParameters(int poolSize, int maxSize,
518: long expiryTime) {
519: synchronized (this ) {
520: if (cleaner != null)
521: cleaner.halt();
522:
523: this .poolSize = Math.max(poolSize, 0);
524: this .maxSize = Math.max(maxSize, 0);
525: this .expiryTime = Math.max(expiryTime, 0);
526: if (this .maxSize > 0 && this .maxSize < this .poolSize)
527: this .maxSize = this .poolSize;
528: resetHitCounter();
529:
530: // Update pooled items to use new expiry
531: TimeWrapper tw = null;
532: for (Iterator iter = free.iterator(); iter.hasNext();) {
533: tw = (TimeWrapper) iter.next();
534: tw.setLiveTime(expiryTime);
535: }
536: // Creates cleaner thread with check interval of at most 5 seconds.
537: if (this .expiryTime > 0) {
538: long iVal = Math.min(5000, this .expiryTime / 5);
539: (cleaner = new Cleaner(this , iVal)).start();
540: }
541: }
542: if (debug) {
543: String info = "pool=" + this .poolSize + ",max="
544: + this .maxSize + ",expiry=";
545: info += this .expiryTime == 0 ? "none" : this .expiryTime
546: + "ms";
547: log("Parameters changed (" + info + ")");
548: }
549: fireParametersChangedEvent();
550: }
551:
552: /**
553: * Returns the total number of objects held (available and checked-out).
554: */
555: public final synchronized int getSize() {
556: return free.size() + used.size();
557: }
558:
559: /**
560: * Returns the number of items that are currently checked-out.
561: */
562: public final synchronized int getCheckedOut() {
563: return used.size();
564: }
565:
566: /**
567: * Returns the number of items held in the pool that are free to be checked-out.
568: */
569: public final synchronized int getFreeCount() {
570: return free.size();
571: }
572:
573: /**
574: * Returns hit rate of the pool as a percentage.
575: * The hit rate is the proportion of requests for a connection which result
576: * in the return of a connection which is in the pool, rather than which
577: * results in the creation of a new connection.
578: */
579: public final float getHitRate() {
580: return (requests == 0) ? 0 : (((float) hits / requests) * 100f);
581: }
582:
583: /**
584: * Resets the counter for determining the pool's hit rate.
585: */
586: protected final void resetHitCounter() {
587: requests = hits = 0;
588: }
589:
590: /**
591: * Sets the pool access method to FIFO (first-in, first-out: a queue).
592: */
593: protected final void setAccessFIFO() {
594: accessMethod = ACCESS_FIFO;
595: }
596:
597: /**
598: * Sets the pool access method to LIFO (last-in, first-out: a stack).
599: */
600: protected final void setAccessLIFO() {
601: accessMethod = ACCESS_LIFO;
602: }
603:
604: /**
605: * Sets the pool access method to random (a random connection is selected for check-out).
606: */
607: protected final void setAccessRandom() {
608: accessMethod = ACCESS_RANDOM;
609: }
610:
611: /**
612: * Returns the class to use for the pool collection.
613: * This can be over-ridden by a sub-class to provide a different List
614: * type for the pool, which may give performance benefits in certain situations.
615: * Only instances of List collections should be used.
616: * (Default: java.util.ArrayList.class)
617: * For those wanting to override this method, pool items are checked-out from
618: * the front of the List - <tt>remove(0)</tt> - and replaced at the end of
619: * the List when checked-in again - <tt>add(Object)</tt>.
620: */
621: protected Class getPoolClass() {
622: return ArrayList.class;
623: }
624:
625: /**
626: * Shuts down the object pool.
627: * If overridden the sub-class should make sure super.finalize() is called.
628: */
629: public void finalize() {
630: if (cleaner != null) {
631: cleaner.halt();
632: cleaner = null;
633: }
634: if (initer != null) {
635: initer.halt();
636: initer = null;
637: }
638: }
639:
640: /**
641: * Flushes the pool of all currently available items, emptying the pool.
642: */
643: public final void flush() {
644: int count = 0;
645: synchronized (this ) {
646: TimeWrapper tw = null;
647: for (Iterator iter = free.iterator(); iter.hasNext();) {
648: tw = (TimeWrapper) iter.next();
649: iter.remove();
650: destroyObject((Reusable) tw.getObject());
651: count++;
652: }
653: }
654: if (count > 0 && debug)
655: log("Flushed all spare items from pool");
656: }
657:
658: /**
659: * Purges expired objects from the pool.
660: * This method is called by the cleaner thread to purge expired items.
661: * @return false if pool is empty after purging (no further purge required until items added), true otherwise
662: */
663: final synchronized boolean purge() {
664: if (debug)
665: log("Checking for expired items");
666: int count = 0;
667: TimeWrapper tw = null;
668: for (Iterator iter = free.iterator(); iter.hasNext();) {
669: tw = (TimeWrapper) iter.next();
670: if (tw.isExpired()) {
671: iter.remove();
672: destroyObject((Reusable) tw.getObject());
673: count++;
674: }
675: }
676: return free.size() > 0 || count > 0;
677: }
678:
679: //**************************
680: // Event-handling methods
681: //**************************
682:
683: /**
684: * Adds an ObjectPoolListener to the event notification list.
685: */
686: public final void addObjectPoolListener(ObjectPoolListener x) {
687: listeners.add(x);
688: }
689:
690: /**
691: * Removes an ObjectPoolListener from the event notification list.
692: */
693: public final void removeObjectPoolListener(ObjectPoolListener x) {
694: listeners.remove(x);
695: }
696:
697: private final void firePoolCheckOutEvent() {
698: if (listeners.isEmpty())
699: return;
700: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
701: ObjectPoolEvent.CHECKOUT);
702: for (Iterator iter = listeners.iterator(); iter.hasNext();)
703: ((ObjectPoolListener) iter.next()).poolCheckOut(poolEvent);
704: }
705:
706: private final void firePoolCheckInEvent() {
707: if (listeners.isEmpty())
708: return;
709: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
710: ObjectPoolEvent.CHECKIN);
711: for (Iterator iter = listeners.iterator(); iter.hasNext();)
712: ((ObjectPoolListener) iter.next()).poolCheckIn(poolEvent);
713: }
714:
715: private final void fireMaxPoolLimitReachedEvent() {
716: if (listeners.isEmpty())
717: return;
718: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
719: ObjectPoolEvent.MAX_POOL_LIMIT_REACHED);
720: for (Iterator iter = listeners.iterator(); iter.hasNext();)
721: ((ObjectPoolListener) iter.next())
722: .maxPoolLimitReached(poolEvent);
723: }
724:
725: private final void fireMaxPoolLimitExceededEvent() {
726: if (listeners.isEmpty())
727: return;
728: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
729: ObjectPoolEvent.MAX_POOL_LIMIT_EXCEEDED);
730: for (Iterator iter = listeners.iterator(); iter.hasNext();)
731: ((ObjectPoolListener) iter.next())
732: .maxPoolLimitExceeded(poolEvent);
733: }
734:
735: private final void fireMaxSizeLimitReachedEvent() {
736: if (listeners.isEmpty())
737: return;
738: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
739: ObjectPoolEvent.MAX_SIZE_LIMIT_REACHED);
740: for (Iterator iter = listeners.iterator(); iter.hasNext();)
741: ((ObjectPoolListener) iter.next())
742: .maxSizeLimitReached(poolEvent);
743: }
744:
745: private final void fireMaxSizeLimitErrorEvent() {
746: if (listeners.isEmpty())
747: return;
748: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
749: ObjectPoolEvent.MAX_SIZE_LIMIT_ERROR);
750: for (Iterator iter = listeners.iterator(); iter.hasNext();)
751: ((ObjectPoolListener) iter.next())
752: .maxSizeLimitError(poolEvent);
753: }
754:
755: private final void fireParametersChangedEvent() {
756: if (listeners == null || listeners.isEmpty())
757: return;
758: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
759: ObjectPoolEvent.PARAMETERS_CHANGED);
760: for (Iterator iter = listeners.iterator(); iter.hasNext();)
761: ((ObjectPoolListener) iter.next())
762: .poolParametersChanged(poolEvent);
763: }
764:
765: private final void firePoolReleasedEvent() {
766: if (listeners.isEmpty())
767: return;
768: ObjectPoolEvent poolEvent = new ObjectPoolEvent(this ,
769: ObjectPoolEvent.POOL_RELEASED);
770: for (Iterator iter = listeners.iterator(); iter.hasNext();)
771: ((ObjectPoolListener) iter.next()).poolReleased(poolEvent);
772: }
773:
774: /**
775: * Thread to perform clean-up of expired objects in pool.
776: * Each time nothing is cleaned because the pool is empty the cleaner waits
777: * until an item is returned, when it is woken up and starts cleaning again.
778: */
779: final class Cleaner extends Thread {
780: private ObjectPool pool;
781: private long interval;
782: private boolean stopped;
783:
784: Cleaner(ObjectPool pool, long interval) {
785: this .setName("CleanerThread_"
786: + Integer.toString(cleanerCount++));
787: this .pool = pool;
788: this .interval = interval;
789: }
790:
791: public void start() {
792: stopped = false;
793: super .start();
794: }
795:
796: /**
797: * Safely stops the thread from running.
798: */
799: public void halt() {
800: if (!isHalted()) {
801: stopped = true;
802: interrupt(); // Wake cleaner if necessary
803: }
804: }
805:
806: /**
807: * Returns whether the thread has been halted.
808: */
809: public boolean isHalted() {
810: return stopped;
811: }
812:
813: /**
814: * Handles the expiry of old objects.
815: */
816: public void run() {
817: while (pool.cleaner == Thread.currentThread() && !stopped) {
818: synchronized (pool) {
819: if (!pool.purge()) {
820: try {
821: pool.wait();
822: } catch (InterruptedException e) {
823: }
824: }
825: }
826: if (!stopped) {
827: try {
828: sleep(interval);
829: } catch (InterruptedException e) {
830: }
831: }
832: }
833: }
834: }
835:
836: /**
837: * Thread to initialize items in pool.
838: * This thread simply performs a check-out/in of new items up to the specified
839: * number to ensure the pool is populated.
840: */
841: private class InitThread extends Thread {
842: private int num;
843: private boolean stopped = false;
844:
845: private InitThread(int num) {
846: // Ensure 0 < num < poolSize.
847: this .num = Math.min(poolSize, Math.max(num, 0));
848: }
849:
850: public void halt() {
851: stopped = true;
852: }
853:
854: /**
855: * Populates the pool with the given number of database connections.
856: * If the pool already contains open connections then they will be counted
857: * towards the number created by this method.
858: */
859: public void run() {
860: if (num > 0 && num <= poolSize && getSize() < num) {
861: int count = 0;
862: while (!stopped && getSize() < num && num <= poolSize) {
863: try {
864: Reusable o = create();
865: if (o == null)
866: throw new RuntimeException(
867: "Null item created");
868: else {
869: free.add(new TimeWrapper(null, o,
870: expiryTime));
871: count++;
872: if (debug)
873: log("Initialized new item in pool");
874: }
875: } catch (Exception ex) {
876: log(ex, "Unable to initialize items in pool");
877: stopped = true;
878: }
879: }
880: if (debug)
881: log("Initialized pool with " + count + " new item"
882: + (count > 1 ? "s" : ""));
883: }
884: }
885: }
886: }
|