001: /**
002: * Library name : Primrose - A Java Database Connection Pool.
003: * Published by Ben Keeping, http://primrose.org.uk .
004: * Copyright (C) 2004 Ben Keeping, primrose.org.uk
005: * Email: Use "Contact Us Form" on website
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */package uk.org.primrose.pool.core;
021:
022: import uk.org.primrose.DebugLogger;
023: import uk.org.primrose.Logger;
024: import uk.org.primrose.Constants;
025: import uk.org.primrose.Util;
026: import uk.org.primrose.pool.*;
027: import java.sql.*;
028: import java.util.*;
029: import uk.org.primrose.pool.core.loadrules.*;
030:
031: public class Pool extends PoolData {
032: /**
033: * CTOR - init some stuff
034: */
035: public Pool() {
036: lock = new PoolLock();
037: DebugLogger.log("[Pool:" + this
038: + " ... Creating new lock object : " + lock);
039: }
040:
041: /**
042: * Get a pooled connection.
043: * If the db is down, or all connections in the pool are busy,
044: * then wait until we can find a connection ...
045: * unless 1) queueConnectionRequests is false,
046: * or 2) waitForConnectionIfDatabaseIsDown is false
047: * whereupon we error, and the client will see a SQLException from
048: * the data source object linked to this pool
049:
050: */
051: public final Connection getConnection() throws PoolException {
052: long lid = ++gid;
053:
054: if (DebugLogger.getEnabled())
055: DebugLogger.log("[Pool@" + poolName + ",id=" + lid
056: + "] getConnection() start");
057: // If we are not running in pooled more, then
058: // just give them a dedicated connection
059: if (!bRunPooledMode)
060: return getNonPooledConnection(lid);
061:
062: // If all connections are busy, then this throws PoolIsFulLException
063: // if the pool is configured so that it does not queue connection requests
064: Connection c = null;
065:
066: // If we want to wait for a connection if the db is down
067: // then hide the CannotConnectException
068: // Else try and get a connection, and throw CannotConnectException (from the internalGetConnection() method)
069: // if we cannot get one ...
070:
071: try {
072: c = internalGetConnection(lid);
073: } catch (CannotConnectException cce) {
074: if (DebugLogger.getEnabled())
075: DebugLogger.log("[Pool@" + poolName + ",id=" + lid
076: + "] getConnection() Got exception("
077: + cce.getClass().getName()
078: + ") getting connection ...");
079: // Only throw an exception if we don't want to wait
080: // and there is no failoverPool
081: if (!bWaitForConnectionIfDatabaseIsDown
082: && failoverPool == null) {
083: throw cce;
084: }
085:
086: if (failoverPool != null) {
087: notifyExceptionEvent();
088: }
089: }
090:
091: // Got a connection - return it
092: if (c != null) {
093: if (DebugLogger.getEnabled())
094: DebugLogger.log("[Pool@" + poolName + ",id=" + lid
095: + "] getConnection() got conn OK - returning");
096: return c;
097: }
098:
099: // If we get here, then it means that either the db is down
100: // and we want to wait until it is up,
101: // or the pool is full, and we want to wait till its not
102: if (DebugLogger.getEnabled())
103: DebugLogger.log("[Pool@" + poolName + ",id=" + lid
104: + "] no connection available - waitingThreads("
105: + (numberOfWaitingThreads + 1) + ")");
106: numberOfWaitingThreads++;
107: return getConnectionWait(lid);
108: }
109:
110: /**
111: * Can't find a connection - loop until we can ...
112: * unless 1) queueConnectionRequests is false,
113: * or 2) waitForConnectionIfDatabaseIsDown is false
114: * whereupon we error, and the client will see a SQLException from
115: * the data source object linked to this pool
116: */
117: private final Connection getConnectionWait(long id)
118: throws PoolException {
119: if (DebugLogger.getEnabled())
120: DebugLogger.log("[Pool@" + poolName + ",id=" + id
121: + "] getConnectionWait() start");
122: try {
123: Thread.sleep(250);
124: } catch (InterruptedException e) {
125: }
126:
127: if (DebugLogger.getEnabled())
128: DebugLogger.log("[Pool@" + poolName + ",id=" + id
129: + "] After 250ms sleep, try to get connection ...");
130: Connection c = null;
131: try {
132: c = internalGetConnection(id);
133: } catch (CannotConnectException cce) {
134: if (DebugLogger.getEnabled())
135: DebugLogger.log("[Pool@" + poolName + ",id=" + id
136: + "] getConnectionWait() Got exception("
137: + cce.getClass().getName()
138: + ") getting connection ...");
139: // Only throw an exception if we don't want to wait
140: // and there is no failoverPool
141: if (!bWaitForConnectionIfDatabaseIsDown
142: && failoverPoolObj != null) {
143: throw cce;
144: }
145:
146: if (failoverPoolObj == null && failoverPool != null) {
147: notifyExceptionEvent();
148: }
149: }
150:
151: // Still cannot find a connection ... loop again
152: if (c == null) {
153: int numberOfActiveConnections = numberOfActiveConnections();
154: logger
155: .verbose("[Pool@"
156: + poolName
157: + ",id="
158: + id
159: + "] getConnectionWait() Could not find a connection ("
160: + numberOfActiveConnections
161: + " active) - sleeping, and trying again");
162: return getConnectionWait(id);
163: }
164:
165: // yay got one ... decrement the number of waiting threads and return the connection
166: numberOfWaitingThreads--;
167: if (DebugLogger.getEnabled())
168: DebugLogger
169: .log("[Pool@"
170: + poolName
171: + ",id="
172: + id
173: + "] getConnectionWait() got connection now, returning; numberOfWaitingThreads : "
174: + numberOfWaitingThreads);
175: return c;
176: }
177:
178: /**
179: * Initialize a ConnectionHolder for when handing out a new pooled connection
180: */
181: private final void initializeConnection(ConnectionHolder ch, long id) {
182: ch.numberOfOpens++;
183: try {
184: throw new Exception();
185: } catch (Exception e) {
186: ch.callStack = e.getStackTrace();
187: }
188:
189: ch.connOpenedDate = System.currentTimeMillis();
190: ch.status = CONNECTION_ACTIVE;
191: ch.id = id;
192: ch.resultsetObjects = new Stack<PoolResultSet>();
193: ch.statementObjects = new Stack<PoolStatement>();
194: }
195:
196: /**
197: * Internal get Connection method
198: * Throws PoolHasNoFreeConnections IFF there are no free connections and queueConnectionRequests == false.
199: * Throws CannotConnectException if the pool is not full, but we cannot get a connection, or
200: * an inactive connection is invalid, and we cannot get a new connection to replace it
201: */
202: private final Connection internalGetConnection(long id)
203: throws PoolException {
204: logger.verbose("[Pool@" + poolName + ",id=" + id
205: + "] internalGetConnection() called ...");
206:
207: if (poolAccessLocked) {
208: logger.warn("[Pool@" + poolName + ",id=" + id
209: + "] getConnection() Pool access is locked ...");
210: throw new PoolException("Access to the pool@" + poolName
211: + " is locked");
212: }
213:
214: // Have we failed over ?
215: if (failoverPoolObj != null) {
216: return failoverPoolObj.internalGetConnection(id);
217: }
218:
219: ConnectionHolder retCH = null;
220:
221: // We want to synchonize access to the core connection list
222: if (DebugLogger.getEnabled())
223: DebugLogger.log("[Pool@" + poolName + ",id=" + id
224: + "] intGetConn() About to synch on lock");
225: synchronized (lock) {
226: long now = System.currentTimeMillis();
227:
228: // Loop the connections, and try to find an inactive connection
229: if (DebugLogger.getEnabled())
230: DebugLogger
231: .log("[Pool@"
232: + poolName
233: + ",id="
234: + id
235: + "] intGetConn() About to loop connections, size : "
236: + connections.size());
237: for (ConnectionHolder ch : connections) {
238: if (DebugLogger.getEnabled())
239: DebugLogger.log("[Pool@" + poolName + ",id=" + id
240: + "] intGetConn() Connection : "
241: + ch.toString());
242: if (ch.status == CONNECTION_INACTIVE) {
243: // Initialize the holder and connection
244: initializeConnection(ch, id);
245: retCH = ch;
246:
247: // Cycle connections past their configured number of calls
248: // Dump it and go back for another
249: if (iCycleConnections > -1) {
250: if (ch.numberOfOpens == iCycleConnections) {
251: logger
252: .info("[Pool@"
253: + poolName
254: + ",id="
255: + id
256: + "] getConnection() Dumping "
257: + ch.conn
258: + " because it has executed its max number of calls ("
259: + iCycleConnections + ")");
260: try {
261: ch.conn.closePhysical();
262: } catch (SQLException sqle) {
263: logger.printStackTrace(sqle);
264: }
265: connections.remove(ch);
266:
267: return internalGetConnection(id);
268: }
269: }
270:
271: // Reset connection defaults
272: // If this errors, then the connection will be closed,
273: // and removed from the global connections list
274: setConnectionDefaults(ch, id);
275:
276: logger.verbose("[Pool@" + poolName + ",id=" + id
277: + "] getConnection() using id "
278: + ch.conn.hashCode() + ", "
279: + numberOfActiveConnections()
280: + " in use, caller : " + getCallerString());
281:
282: // found one ... break
283: totalConnectionsHandedOut++;
284: break;
285: }
286: }
287:
288: // No connections in pool, or all active ... try and load a new one
289: if (retCH == null) {
290: int numberOfLoadedConnections = connections.size();
291: // Load a connection if necessary, and if we can (ie active is less than base)
292: // Pool is not busy, and there is room to load a new connection
293: if (numberOfLoadedConnections < iBase) {
294:
295: logger
296: .verbose("[Pool@"
297: + poolName
298: + ",id="
299: + id
300: + "] getConnection() - No loaded connections in pool ... loaded("
301: + numberOfLoadedConnections
302: + ") of base(" + iBase + ")");
303:
304: logger
305: .verbose("[Pool@"
306: + poolName
307: + ",id="
308: + id
309: + "] getConnection() loading new connection ...");
310: // Load a new connection
311: ConnectionHolder ch = loadConnection(false, id);
312: // Initialize the holder and connection
313: initializeConnection(ch, id);
314: // Add it into the pool (marked as ACTIVE)
315: connections.addElement(ch);
316: // Increment the number of connections handed out
317: totalConnectionsHandedOut++;
318: retCH = ch;
319: } else if (numberOfLoadedConnections == iBase) {
320: logger
321: .verbose("[Pool@"
322: + poolName
323: + ",id="
324: + id
325: + "] getConnection() - All loaded conn's in use... loaded("
326: + numberOfLoadedConnections
327: + ") of base(" + iBase + ")");
328:
329: StringBuffer stack = new StringBuffer();
330: try {
331: throw new Exception();
332: } catch (Exception e) {
333: StackTraceElement[] els = e.getStackTrace();
334: for (StackTraceElement el : els) {
335: stack.append("\t");
336: stack.append(el.toString());
337: stack.append("\n");
338: }
339:
340: }
341:
342: logger.email(Constants.NO_FREE_CONNECTIONS_EVENT,
343: "Pool has no free connections - in use("
344: + numberOfLoadedConnections
345: + "), base(" + iBase + ") : "
346: + new java.util.Date() + "\n"
347: + stack.toString());
348:
349: // They don't want to wait for a connection, but
350: // wish to see an error populated through to the client
351: // if there are no connections available
352: if (!bQueueConnectionRequests) {
353: throw new PoolHasNoFreeConnections();
354: }
355:
356: }
357:
358: // Loaded conn OK ... recurse to pick up
359: //return internalGetConnection();
360: }
361:
362: logger.verbose("[Pool@" + poolName + ",id=" + id
363: + "] getConnection() took "
364: + (System.currentTimeMillis() - now) + "ms");
365: } // end synch block
366:
367: if (retCH == null)
368: return null;
369:
370: // Check if the connection is OK
371: // If it is not, dump it, and load another, and then recurse back
372: // If it looks like the db is down, then depending on the value
373: // of waitForConnectionIfDatabaseIsDown, then either throw an exception
374: // or recurse
375: boolean conOK = checkIfConnectionIsValid(retCH.conn, id);
376:
377: if (!conOK) {
378: logger.verbose("[Pool@" + poolName + ",id=" + id
379: + "] getConnection() Dumping " + retCH.conn
380: + " because it failed validity checks.");
381: try {
382: retCH.conn.closePhysical();
383: } catch (SQLException sqle) {
384: logger.printStackTrace(sqle);
385: }
386: connections.remove(retCH);
387:
388: this .notifyExceptionEvent();
389: try {
390: Thread.sleep(250);
391: } catch (InterruptedException ie) {
392: }
393: return internalGetConnection(id);
394: }
395:
396: return retCH.conn;
397: }
398:
399: /**
400: * Get who called the getConnection() method
401: * @return String
402: */
403: private String getCallerString() {
404: try {
405: throw new Exception();
406: } catch (Exception e) {
407: int maxBacktraceCount = 5;
408: StackTraceElement[] ste = e.getStackTrace();
409: StringBuffer caller = new StringBuffer(100);
410: boolean start = false;
411: for (int i = 0; i < ste.length; i++) {
412: if (ste[i].getClassName()
413: .endsWith("PrimroseDataSource")) {
414: start = true;
415: continue;
416: }
417:
418: if (start && maxBacktraceCount > 0) {
419: maxBacktraceCount--;
420: if (maxBacktraceCount == 0) {
421: caller.append(ste[i].getFileName() + "[method:"
422: + ste[i].getMethodName() + ",line:"
423: + ste[i].getLineNumber() + "]");
424: } else {
425: caller.append(ste[i].getFileName() + "[method:"
426: + ste[i].getMethodName() + ",line:"
427: + ste[i].getLineNumber() + "], ");
428: }
429: }
430: }
431: return caller.toString();
432: }
433:
434: }
435:
436: /**
437: * Apply default connection properties to the connection
438: * Called from fill() when a brand new connection is added to the pool,
439: * and from put() when a connection is returned to the pool
440: * If the check methods fail (SQLException), the underlying connection will be closed down,
441: * and removed from the pool list
442: */
443: protected final void setConnectionDefaults(ConnectionHolder ch,
444: long id) throws PoolException {
445: if (DebugLogger.getEnabled())
446: DebugLogger.log("[Pool@" + poolName + ",id=" + id
447: + "] setConnectionDefaults() start");
448: Connection c = ch.conn;
449: // set the default auto commit value
450: try {
451: if (c.getAutoCommit() != bConnectionAutoCommit) {
452: // don't log changes to driver defaults if the connection
453: // is brand new
454: if (ch.numberOfOpens > 0) {
455: logger
456: .warn("[Pool@"
457: + poolName
458: + ",id="
459: + id
460: + "] setConnectionDefaults() : Checking autocommit value : Looks like someone has changed it from the default, and has not set it back. Default should be '"
461: + bConnectionAutoCommit
462: + "', but the connection value is '"
463: + c.getAutoCommit() + "'");
464: }
465: c.setAutoCommit(bConnectionAutoCommit);
466:
467: }
468: } catch (SQLException sqle) {
469: logger.printStackTrace(sqle);
470: ch.closeBehaviour = Pool.ON_CLOSE_SHOULD_DIE;
471: ch.conn.close();
472: connections.remove(ch);
473: logger
474: .warn("[Pool@"
475: + poolName
476: + ",id="
477: + id
478: + "] setConnectionDefaults() : Error checking auto commit. Connection will be dumped.");
479: notifyExceptionEvent();
480: throw new CannotConnectException(
481: "Checking auto commit value errored : "
482: + sqle.toString(), sqle);
483: }
484:
485: // set the default transaction isolation level
486: // if the user has specified they want it in primrose.config.
487: // Else, just leave it as the default
488: if (iConnectionTransactionIsolation != -1) {
489: try {
490: // if it is -1, then set the default
491: //if (iConnectionTransactionIsolation == -1) {
492: // setInternalConnectionTransactionIsolation(null);
493: //}
494: // if the connections setting does not equal the pool's,
495: // then set it (and log that somone changed it, but did not set it back
496: if (c.getTransactionIsolation() != iConnectionTransactionIsolation) {
497: // don't log changes to driver defaults if the connection
498: // is brand new
499: if (ch.numberOfOpens > 0) {
500: logger
501: .warn("[Pool@"
502: + poolName
503: + ",id="
504: + id
505: + "] setConnectionDefaults() : Checking transaction isolation level : Looks like someone has changed it from the default, and has not set it back. Default should be '"
506: + getInternalConnectionTransactionIsolation()
507: + "', but the connection value is '"
508: + getInternalConnectionTransactionIsolation(c
509: .getTransactionIsolation())
510: + "'");
511: }
512: c
513: .setTransactionIsolation(iConnectionTransactionIsolation);
514: }
515: } catch (SQLException sqle) {
516: logger.printStackTrace(sqle);
517: ch.closeBehaviour = Pool.ON_CLOSE_SHOULD_DIE;
518: ch.conn.close();
519: connections.remove(ch);
520: logger
521: .warn("[Pool@"
522: + poolName
523: + ",id="
524: + id
525: + "] setConnectionDefaults() : Error checking transaction isolation level. Connection will be dumped.");
526: notifyExceptionEvent();
527: throw new CannotConnectException(
528: "Checking transaction isolation level value errored : "
529: + sqle.toString(), sqle);
530: }
531: }
532:
533: if (DebugLogger.getEnabled())
534: DebugLogger.log("[Pool@" + poolName + ",id=" + id
535: + "] setConnectionDefaults() leave");
536: }
537:
538: /**
539: * Check if a connection is valid. This calls isClosed() (once we go to Java 6, we can call isValid())
540: * If user has configured "checkSQL" option, we run that also.
541: * If either checks fail, the connection is dumped.
542: */
543: private final boolean checkIfConnectionIsValid(Connection c, long id) {
544: if (DebugLogger.getEnabled())
545: DebugLogger.log("[Pool@" + poolName + ",id=" + id
546: + "] checkIfConnectionIsValid() start");
547:
548: long now = System.currentTimeMillis();
549:
550: boolean checkret = false;
551:
552: try {
553: checkret = c.isClosed();
554: checkret = true;
555: } catch (SQLException e) {
556: logger
557: .error("[Pool@"
558: + poolName
559: + ",id="
560: + id
561: + "] checkIfConnectionIsValid() : Error calling isClosed() on connection "
562: + c + " : " + e);
563: }
564:
565: // If its false (ie isClosed() returned false, then return false
566: if (!checkret)
567: return false;
568:
569: // Only run a check if they want it
570: if (checkSQL != null && checkSQL.length() > 0) {
571: logger
572: .verbose("[Pool@"
573: + poolName
574: + ",id="
575: + id
576: + "] checkIfConnectionIsValid() : running checkSQL : "
577: + checkSQL);
578: checkret = ((PoolConnection) c).runCheckSQL(checkSQL);
579:
580: }
581: logger.verbose("[Pool@" + poolName + ",id=" + id
582: + "] checkIfConnectionIsValid(" + checkret
583: + ") : took " + (System.currentTimeMillis() - now)
584: + " ms");
585:
586: return checkret;
587: }
588:
589: /**
590: * Get a non pooled connection - ie just a dedicated connection
591: * that someone can use once and once only.
592: * So when the client calls close(), the connection dies off
593: * @return a Connection object
594: */
595: private final Connection getNonPooledConnection(long id)
596: throws PoolException {
597: if (DebugLogger.getEnabled())
598: DebugLogger.log("[Pool@" + poolName + ",id=" + id
599: + "] getNonPooledConnection() start");
600:
601: ConnectionHolder ch = loadConnection(false, id);
602: initializeConnection(ch, id);
603: // override the close behaviour because its a non-pooled connection
604: ch.closeBehaviour = ON_CLOSE_SHOULD_DIE;
605:
606: if (DebugLogger.getEnabled())
607: DebugLogger.log("[Pool@" + poolName + ",id=" + id
608: + "] getNonPooledConnection() end : " + ch.conn);
609: return ch.conn;
610: }
611:
612: /**
613: * Get a ConnectionHolder object, with the connection in it.
614: *
615: * @param addToList - should the ConnectionHolder be added to the list
616: * of pooled connections
617: * @return a ConnectionHolder object which contains the connection
618: * @throws PoolException
619: */
620: private final ConnectionHolder loadConnection(boolean addToList,
621: long id) throws PoolException {
622: if (DebugLogger.getEnabled())
623: DebugLogger.log("[Pool@" + poolName + ",id=" + id
624: + "] loadConnection() start");
625: Connection raw = Util.getConnection(driverClass, driverURL,
626: user, password);
627: ConnectionHolder ch = new ConnectionHolder();
628: PoolConnection pc = new PoolConnection(raw, ch);
629: ch.conn = pc;
630: ch.closeBehaviour = ON_CLOSE_SHOULD_REUSE;
631: ch.status = CONNECTION_INACTIVE;
632: ch.lock = lock;
633: ch.poolName = poolName;
634: ch.bDumpConnectionOnSQLException = this .bDumpConnectionOnSQLException;
635: ch.myPool = this ;
636: ch.logger = this .logger;
637:
638: setConnectionDefaults(ch, id);
639:
640: if (addToList) {
641: connections.addElement(ch);
642: }
643: return ch;
644: }
645:
646: /**
647: * Close down all connection objects, and remove them from the connection list
648: * If force is true, then close the connection down immediately, else
649: * leave them to finish their job, and then close them. This means that pools can
650: * be stopped/started with no impact on live connections (ie safely).
651: */
652: public final void stop(boolean force) throws PoolException {
653: if (bPoolHasBeenShutdown) {
654: logger
655: .warn("[Pool@"
656: + poolName
657: + "] stop() Pool has already been shutdown ... not doing it twice.");
658: return;
659: }
660: logger.email(Constants.STOP_EVENT, "Pool stopping at : "
661: + new java.util.Date());
662:
663: logger
664: .info("[Pool@" + poolName
665: + "] stop() Stopping pool with force=" + force
666: + " ...");
667: // Lock access to the pool
668: poolAccessLocked = true;
669:
670: // Kill the cutback failover thread if it exists
671: if (failoverCutBackObj != null) {
672: failoverCutBackObj.stopIt();
673: }
674:
675: // Close down each connection
676: for (ConnectionHolder ch : connections) {
677: // If we are forcing a close, just dump it
678: // Else, only dump if not busy
679: // If they are busy, and we are not forcing close, then mark the connection
680: // as not returning to the pool once its free (ie dump after work is done)
681: try {
682: if (force) {
683: ch.conn.closePhysical();
684: } else {
685: if (ch.status == CONNECTION_INACTIVE) {
686: ch.conn.closePhysical();
687: } else {
688: ch.closeBehaviour = ON_CLOSE_SHOULD_DIE;
689: }
690: }
691: } catch (SQLException e) {
692: logger.printStackTrace(e);
693: }
694: }
695:
696: connections.removeAllElements();
697:
698: logger.info("[Pool@" + poolName
699: + "] stop() Shutting down pool monitor.");
700: if (monitor != null) {
701: monitor.shutdown();
702: }
703:
704: logger.info("[Pool@" + poolName + "] stop() Stop Complete.");
705: logger.close();
706: bPoolHasBeenShutdown = true;
707: }
708:
709: /**
710: * Start the pool, filling it with the configured base of connections
711: */
712: public final void start() throws PoolException {
713: // set up the logger
714: setUpLogger();
715: gid = 0L;
716: bPoolHasBeenShutdown = false;
717:
718: logger.verbose("[Pool@" + poolName + "] Checking " + poolName
719: + " parameters ...");
720:
721: for (LoadRule rule : loadRules) {
722: rule.runCheck(this , this .logger);
723: }
724:
725: logger.email(Constants.START_EVENT, "Pool starting at : "
726: + new java.util.Date());
727:
728: // print some verbose logging
729: logger.info("[Pool@" + poolName + "] STARTING " + poolName
730: + " ...");
731: Util.printGetMethodValues("[Pool@" + poolName
732: + "] config item : ", logger, PoolConfigImpl.class,
733: this );
734:
735: // Lock access to the pool
736: poolAccessLocked = true;
737:
738: // Make a new list
739: connections = new Vector<ConnectionHolder>();
740: numberOfWaitingThreads = 0;
741: totalConnectionsHandedOut = 0;
742: if (iNumberOfConnectionsToInitializeWith > iBase) {
743: logger
744: .warn("[Pool@"
745: + poolName
746: + "] start() The number of connections to initialise with is greater than the number of base connections ... adjusting init number to base : "
747: + iBase);
748: iNumberOfConnectionsToInitializeWith = iBase;
749: }
750: logger.verbose("[Pool@" + poolName + "] start() Loading "
751: + iNumberOfConnectionsToInitializeWith
752: + " Connection(s) on init");
753:
754: for (int i = 0; i < iNumberOfConnectionsToInitializeWith; i++) {
755: try {
756: Connection pc = loadConnection(true, i).conn;
757: if (i == 0) {
758: DatabaseMetaData conMD = pc.getMetaData();
759: logger.info("[Pool@" + poolName
760: + "] start() Primrose version "
761: + Constants.VERSION + ", release date "
762: + Constants.RELEASE_DATE);
763: logger.info("[Pool@" + poolName
764: + "] start() JDBC Driver Name: "
765: + conMD.getDriverName());
766: logger.info("[Pool@" + poolName
767: + "] start() JDBC Driver Version: "
768: + conMD.getDriverVersion());
769: }
770: } catch (Throwable t) {
771: logger
772: .warn("[Pool@"
773: + poolName
774: + "] start() Could not connect to db - is this OK ?");
775: logger.printStackTrace(t);
776: }
777: }
778:
779: logger.info("[Pool@" + poolName
780: + "] start() Starting new pool monitor.");
781: monitor = new PoolMonitor(this , logger);
782: monitor.start();
783:
784: logger.info("[Pool@" + poolName + "] start() Load complete.");
785:
786: // Unlock access to the pool - make it available
787: poolAccessLocked = false;
788: }
789:
790: public final Vector<ConnectionHolder> getPoolConnections() {
791: return connections;
792: }
793:
794: /**
795: * Restart the pool, calling stop(), then start()
796: */
797: public final void restart(boolean forceStop) throws PoolException {
798: logger.info("[Pool@" + poolName
799: + "] restart() Restarting pool ...");
800: stop(forceStop);
801: start();
802: logger.info("[Pool@" + poolName
803: + "] restart() Restart Complete ...");
804: }
805:
806: public Logger getLogger() {
807: return logger;
808: }
809:
810: /**
811: * If a SQLException occurs and the parameter 'dumpConnectionOnSQLException' is true (default)
812: * then this method is called.
813: * If the config requires emails to be sent on SQLExceptions, then send it.
814: * If we require notification of a possible DB crash, then see if we have, using the
815: * 'onExceptionCheckSQL' parameter SQL.
816: * If the config requires failover, then attempt that (if the db has crashed).
817: */
818: protected void notifyExceptionEvent() {
819: if (emailEvents != null) {
820: boolean notifyException = emailEvents.toUpperCase()
821: .indexOf(Constants.EXCEPTION_EVENT.toUpperCase()) > -1;
822: boolean notifyCrash = emailEvents.toUpperCase().indexOf(
823: Constants.DBCRASH_EVENT.toUpperCase()) > -1;
824:
825: if (notifyException) {
826: logger.email(Constants.EXCEPTION_EVENT,
827: "SQLException has occured in pool " + poolName);
828: }
829:
830: if (notifyCrash) {
831: boolean bHasCrashed = hasDbCrashed();
832:
833: if (notifyCrash && bHasCrashed) {
834: logger.email(Constants.DBCRASH_EVENT,
835: "Database seems to have crashed ! Driver URL : "
836: + driverURL);
837: }
838: // If we don't want failover, or we already have failed over, then ignore
839: if (failoverPool != null && failoverPoolObj == null) {
840: attemptFailover();
841: }
842: }
843: }
844:
845: // If we want failover, and we habe not already failed over, then
846: // attempt a failover
847: if (failoverPool != null && failoverPoolObj == null) {
848: boolean bHasCrashed = hasDbCrashed();
849: if (bHasCrashed) {
850: attemptFailover();
851: }
852: }
853: }
854:
855: /*
856: * Work out if the db has crashed by running
857: */
858: private boolean hasDbCrashed() {
859: boolean bHasCrashed = false;
860:
861: logger
862: .verbose("[Pool@"
863: + poolName
864: + "] About to see if DB has crashed (get new connection & run onExceptionCheckSQL)...");
865: try {
866: if (DebugLogger.getEnabled())
867: DebugLogger.log("[Pool@" + poolName
868: + "] hasDbCrashed() Loading connection ...");
869: //Throws exception if fails ( == yes, failover)
870: ConnectionHolder ch = loadConnection(false, -7777);
871: Connection c = ch.conn;
872: // false == fail check, true == passed check
873: // so bHasCrashed = !checkval
874: if (DebugLogger.getEnabled())
875: DebugLogger
876: .log("[Pool@"
877: + poolName
878: + "] hasDbCrashed() Running onExceptionCheckSQL statement ...");
879: bHasCrashed = ((PoolConnection) c)
880: .runCheckSQL(onExceptionCheckSQL);
881: if (DebugLogger.getEnabled())
882: DebugLogger.log("[Pool@" + poolName
883: + "] hasDbCrashed() runCheckSQL returned "
884: + bHasCrashed);
885:
886: bHasCrashed = !bHasCrashed;
887:
888: try {
889: if (c != null)
890: c.close();
891: } catch (SQLException sqle) {
892: }
893: } catch (PoolException pe) {
894: logger.printStackTrace(pe);
895: bHasCrashed = true;
896: }
897:
898: if (DebugLogger.getEnabled())
899: DebugLogger.log("[Pool@" + poolName + "] hasDbCrashed ? : "
900: + bHasCrashed);
901: return bHasCrashed;
902: }
903:
904: /*
905: * If failoverPool is set, and we have failed over, and then the DB is back up
906: * then cutback to this pool.
907: */
908: public void cutbackFromFailoverPool() {
909: logger.info("[Pool@" + poolName
910: + "] Cutting back to this pool from failoverPool "
911: + failoverPool);
912: logger.email(Constants.CUTBACK_EVENT,
913: "Cutting back to this pool from failoverPool "
914: + failoverPool);
915: try {
916: stop(false);
917: start();
918: failoverPoolObj = null;
919: logger.info("[Pool@" + poolName
920: + "] Cutback to original pool succeeded");
921: logger.email(Constants.CUTBACK_EVENT,
922: "Cutback to original pool succeeded");
923: } catch (PoolException pe) {
924: logger.error("Cutback failed ...");
925: logger.printStackTrace(pe);
926: logger.email(Constants.CUTBACK_EVENT, "Cutback failed : "
927: + pe);
928: }
929: }
930:
931: /**
932: * If a SQLException occurs and the parameter 'dumpConnectionOnSQLException' is true (default)
933: * and the parameter 'failoverPool' is set, then see if we should failover.
934: */
935: private void attemptFailover() {
936: logger.info("[Pool@" + poolName
937: + "] Now attemping failover to pool '" + failoverPool
938: + "'");
939: logger
940: .email(Constants.FAILOVER_EVENT,
941: "Now attemping failover to pool '"
942: + failoverPool + "'");
943: try {
944: logger.info("[Pool@" + poolName + "] Finding failoverPool("
945: + failoverPool + ")");
946: // Restart and change the pool name
947: failoverPoolObj = PoolLoader.findExistingPool(failoverPool);
948: if (failoverPoolObj == null) {
949: throw new PoolException("Cannot find failoverPool("
950: + failoverPool + ") !");
951: }
952: logger.info("[Pool@" + poolName
953: + "] Stopping this pool ...");
954: stop(true);
955:
956: poolAccessLocked = false;
957: logger
958: .info("[Pool@"
959: + poolName
960: + "] Testing that can get connection from failoverPool ...");
961:
962: Connection c = getConnection();
963: try {
964: if (c != null)
965: c.close();
966: } catch (SQLException sqle) {
967: }
968:
969: logger.info("[Pool@" + poolName
970: + "] Starting failover cutback monitor ...");
971: failoverCutBackObj = new FailoverCutBack(this , logger);
972: failoverCutBackObj.start();
973:
974: logger.info("[Pool@" + poolName
975: + "] Failover complete. Routing all requests to "
976: + poolName + " to " + failoverPool);
977: logger.email(Constants.FAILOVER_EVENT,
978: "Failover complete. Routing all requests to "
979: + poolName + " to " + failoverPool);
980: } catch (PoolException pe) {
981: logger.error("[Pool@" + poolName + "] Failover failed : "
982: + pe.toString());
983: logger.email(Constants.FAILOVER_EVENT, "Failover failed : "
984: + pe.toString());
985: logger.printStackTrace(pe);
986: }
987:
988: }
989:
990: }
|