Pool.java :  » Database-JDBC-Connection-Pool » Primrose » uk » org » primrose » pool » core » Java Open Source

Java Open Source » Database JDBC Connection Pool » Primrose 
Primrose » uk » org » primrose » pool » core » Pool.java
/**
*  Library name : Primrose - A Java Database Connection Pool.
*  Published by Ben Keeping, http://primrose.org.uk .
*  Copyright (C) 2004 Ben Keeping, primrose.org.uk
*  Email: Use "Contact Us Form" on website
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package uk.org.primrose.pool.core;
import uk.org.primrose.DebugLogger;
import uk.org.primrose.Logger;
import uk.org.primrose.Constants;
import uk.org.primrose.Util;
import uk.org.primrose.pool.*;
import java.sql.*;
import java.util.*;
import uk.org.primrose.pool.core.loadrules.*;

public class Pool extends PoolData {
  /**
  *  CTOR - init some stuff
  */
  public Pool() {
    lock = new PoolLock();
    DebugLogger.log("[Pool:" +this +" ... Creating new lock object : " +lock);
  }

  /**
  *  Get a pooled connection.
  *  If the db is down, or all connections in the pool are busy,
  *  then wait until we can find a connection ...
  *   unless 1) queueConnectionRequests is false,
  *  or     2) waitForConnectionIfDatabaseIsDown is false
  *   whereupon we error, and the client will see a SQLException from
  *  the data source object linked to this pool

  */
  public final Connection getConnection() throws PoolException {
    long lid = ++gid;

    if (DebugLogger.getEnabled())  DebugLogger.log("[Pool@" +poolName +",id=" +lid +"] getConnection() start");
    // If we are not running in pooled more, then
    // just give them a dedicated connection
    if (!bRunPooledMode) return getNonPooledConnection(lid);

    // If all connections are busy, then this throws PoolIsFulLException
    // if the pool is configured so that it does not queue connection requests
    Connection c = null;

    // If we want to wait for a connection if the db is down
    // then hide the CannotConnectException
    // Else try and get a connection, and throw CannotConnectException (from the internalGetConnection() method)
    // if we cannot get one ...

    try {
      c = internalGetConnection(lid);
    } catch (CannotConnectException cce) {
      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +lid +"] getConnection() Got exception(" +cce.getClass().getName() +") getting connection ...");
      // Only throw an exception if we don't want to wait
      // and there is no failoverPool
      if (!bWaitForConnectionIfDatabaseIsDown && failoverPool == null) {
        throw cce;
      }

      if (failoverPool != null) {
        notifyExceptionEvent();
      }
    }


    // Got a connection - return it
    if (c != null) {
      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +lid +"] getConnection() got conn OK - returning");
      return c;
    }

    // If we get here, then it means that either the db is down
    // and we want to wait until it is up,
    // or the pool is full, and we want to wait till its not
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +lid +"] no connection available - waitingThreads(" +(numberOfWaitingThreads +1)+")");
    numberOfWaitingThreads++;
    return getConnectionWait(lid);
  }

  /**
  *  Can't find a connection - loop until we can ...
  *   unless 1) queueConnectionRequests is false,
  *  or     2) waitForConnectionIfDatabaseIsDown is false
  *   whereupon we error, and the client will see a SQLException from
  *  the data source object linked to this pool
  */
  private final Connection getConnectionWait(long id) throws PoolException {
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] getConnectionWait() start");
    try { Thread.sleep(250); } catch (InterruptedException e){}

    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] After 250ms sleep, try to get connection ...");
    Connection c = null;
    try {
      c = internalGetConnection(id);
    } catch (CannotConnectException cce) {
      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] getConnectionWait() Got exception(" +cce.getClass().getName() +") getting connection ...");
      // Only throw an exception if we don't want to wait
      // and there is no failoverPool
      if (!bWaitForConnectionIfDatabaseIsDown && failoverPoolObj != null) {
        throw cce;
      }

      if (failoverPoolObj == null && failoverPool != null) {
        notifyExceptionEvent();
      }
    }

    // Still cannot find a connection ... loop again
    if (c == null) {
      int numberOfActiveConnections = numberOfActiveConnections();
      logger.verbose("[Pool@" +poolName +",id=" +id +"] getConnectionWait() Could not find a connection (" +numberOfActiveConnections +" active) - sleeping, and trying again");
      return getConnectionWait(id);
    }

    // yay got one ... decrement the number of waiting threads and return the connection
    numberOfWaitingThreads--;
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] getConnectionWait() got connection now, returning; numberOfWaitingThreads : " +numberOfWaitingThreads);
    return c;
  }

  /**
   *   Initialize a ConnectionHolder for when handing out a new pooled connection
   */
  private final void initializeConnection(ConnectionHolder ch, long id) {
    ch.numberOfOpens++;
    try {
      throw new Exception();
    } catch (Exception e) {
      ch.callStack = e.getStackTrace();
    }

    ch.connOpenedDate = System.currentTimeMillis();
    ch.status = CONNECTION_ACTIVE;
    ch.id = id;
    ch.resultsetObjects = new Stack<PoolResultSet>();
    ch.statementObjects = new Stack<PoolStatement>();
  }

  /**
  *  Internal get Connection method
  *   Throws PoolHasNoFreeConnections IFF there are no free connections and queueConnectionRequests == false.
  *  Throws CannotConnectException if the pool is not full, but we cannot get a connection, or
  *  an inactive connection is invalid, and we cannot get a new connection to replace it
  */
  private final Connection internalGetConnection(long id) throws PoolException {
    logger.verbose("[Pool@" +poolName +",id=" +id +"] internalGetConnection() called ...");

    if (poolAccessLocked) {
      logger.warn("[Pool@" +poolName +",id=" +id +"] getConnection() Pool access is locked ...");
      throw new PoolException("Access to the pool@" +poolName +" is locked");
    }

    // Have we failed over ?
    if (failoverPoolObj != null) {
      return failoverPoolObj.internalGetConnection(id);
    }

    ConnectionHolder retCH = null;

    // We want to synchonize access to the core connection list
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] intGetConn() About to synch on lock");
    synchronized (lock) {
      long now = System.currentTimeMillis();

      // Loop the connections, and try to find an inactive connection
      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] intGetConn() About to loop connections, size : " +connections.size());
      for (ConnectionHolder ch : connections) {
        if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] intGetConn() Connection : " +ch.toString());
        if (ch.status == CONNECTION_INACTIVE) {
          // Initialize the holder and connection
          initializeConnection(ch, id);
          retCH = ch;


          // Cycle connections past their configured number of calls
          // Dump it and go back for another
          if (iCycleConnections > -1) {
            if (ch.numberOfOpens == iCycleConnections) {
              logger.info("[Pool@" +poolName +",id=" +id +"] getConnection() Dumping " +ch.conn +" because it has executed its max number of calls (" +iCycleConnections +")");
              try { ch.conn.closePhysical(); } catch (SQLException sqle) { logger.printStackTrace(sqle); }
              connections.remove(ch);

              return internalGetConnection(id);
            }
          }

          // Reset connection defaults
          // If this errors, then the connection will be closed,
          // and removed from the global connections list
          setConnectionDefaults(ch, id);


          logger.verbose("[Pool@" +poolName +",id=" +id +"] getConnection() using id " +ch.conn.hashCode() +", " +numberOfActiveConnections() +" in use, caller : " +getCallerString());


          // found one ... break
          totalConnectionsHandedOut++;
          break;
        }
      }

      // No connections in pool, or all active ... try and load a new one
      if (retCH == null) {
        int numberOfLoadedConnections = connections.size();
        // Load a connection if necessary, and if we can (ie active is less than base)
        // Pool is not busy, and there is room to load a new connection
        if (numberOfLoadedConnections < iBase) {

          logger.verbose("[Pool@" +poolName +",id=" +id +"] getConnection() - No loaded connections in pool ... loaded(" +numberOfLoadedConnections +") of base(" +iBase +")");

          logger.verbose("[Pool@" +poolName +",id=" +id +"] getConnection() loading new connection ...");
          // Load a new connection
          ConnectionHolder ch = loadConnection(false, id);
          // Initialize the holder and connection
          initializeConnection(ch, id);
          // Add it into the pool (marked as ACTIVE)
          connections.addElement(ch);
          // Increment the number of connections handed out
          totalConnectionsHandedOut++;
          retCH = ch;
        } else if (numberOfLoadedConnections == iBase) {
          logger.verbose("[Pool@" +poolName +",id=" +id +"] getConnection() - All loaded conn's in use... loaded(" +numberOfLoadedConnections +") of base(" +iBase +")");
          
          StringBuffer stack = new StringBuffer();
          try {
            throw new Exception();
          } catch (Exception e) {
            StackTraceElement[] els = e.getStackTrace();
            for (StackTraceElement el : els) {
              stack.append("\t");
              stack.append(el.toString());
              stack.append("\n");
            }                
            
          }
          
          logger.email(Constants.NO_FREE_CONNECTIONS_EVENT, "Pool has no free connections - in use(" +numberOfLoadedConnections  +"), base(" +iBase +") : " +new java.util.Date() +"\n" +stack.toString());

          // They don't want to wait for a connection, but
          // wish to see an error populated through to the client
          // if there are no connections available
          if (!bQueueConnectionRequests) {
            throw new PoolHasNoFreeConnections();
          }



        }

        // Loaded conn OK ... recurse to pick up
        //return internalGetConnection();
      }

      logger.verbose("[Pool@" +poolName +",id=" +id +"] getConnection() took " +(System.currentTimeMillis() - now) +"ms");
    } // end synch block


    if (retCH == null) return null;

    // Check if the connection is OK
    // If it is not, dump it, and load another, and then recurse back
    // If it looks like the db is down, then depending on the value
    // of waitForConnectionIfDatabaseIsDown, then either throw an exception
    // or recurse
    boolean conOK = checkIfConnectionIsValid(retCH.conn, id);

    if (!conOK) {
      logger.verbose("[Pool@" +poolName +",id=" +id +"] getConnection() Dumping " +retCH.conn +" because it failed validity checks.");
      try { retCH.conn.closePhysical(); } catch (SQLException sqle) { logger.printStackTrace(sqle); }
      connections.remove(retCH);

      this.notifyExceptionEvent();
      try { Thread.sleep(250) ; } catch (InterruptedException ie) {}
      return internalGetConnection(id);
    }

    return retCH.conn;
  }

  /**
   * Get who called the getConnection() method
   * @return String
   */
  private String getCallerString() {
    try {
      throw new Exception();
    } catch (Exception e) {
      int maxBacktraceCount = 5;
      StackTraceElement[] ste = e.getStackTrace();
      StringBuffer caller = new StringBuffer(100);
      boolean start = false;
      for (int i = 0; i < ste.length; i++) {
        if (ste[i].getClassName().endsWith("PrimroseDataSource")) {
          start = true;
          continue;
        }

        if (start && maxBacktraceCount > 0) {
          maxBacktraceCount--;
          if (maxBacktraceCount == 0) {
            caller.append(ste[i].getFileName() +"[method:" +ste[i].getMethodName() +",line:" +ste[i].getLineNumber()  +"]");
          } else {
            caller.append(ste[i].getFileName() +"[method:" +ste[i].getMethodName() +",line:" +ste[i].getLineNumber()  +"], ");
          }
        }
      }
      return caller.toString();
    }

  }

  /**
  *  Apply default connection properties to the connection
  *  Called from fill() when a brand new connection is added to the pool,
  *  and from put() when a connection is returned to the pool
  *  If the check methods fail (SQLException), the underlying connection will be closed down,
  *  and removed from the pool list
  */
  protected final void setConnectionDefaults(ConnectionHolder ch, long id) throws PoolException {
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] setConnectionDefaults() start");
      Connection c = ch.conn;
      // set the default auto commit value
      try {
        if (c.getAutoCommit() != bConnectionAutoCommit) {
          // don't log changes to driver defaults if the connection
          // is brand new
          if (ch.numberOfOpens > 0) {
            logger.warn("[Pool@" +poolName +",id=" +id +"] setConnectionDefaults() : Checking autocommit value : Looks like someone has changed it from the default, and has not set it back. Default should be '" +bConnectionAutoCommit +"', but the connection value is '" +c.getAutoCommit() +"'");
          }
          c.setAutoCommit(bConnectionAutoCommit);

        }
      } catch (SQLException sqle) {
        logger.printStackTrace(sqle);
        ch.closeBehaviour = Pool.ON_CLOSE_SHOULD_DIE;
        ch.conn.close();
        connections.remove(ch);
        logger.warn("[Pool@" +poolName +",id=" +id +"] setConnectionDefaults() : Error checking auto commit. Connection will be dumped.");
        notifyExceptionEvent();
        throw new CannotConnectException("Checking auto commit value errored : " +sqle.toString(), sqle);
      }

      // set the default transaction isolation level
      // if the user has specified they want it in primrose.config.
      // Else, just leave it as the default
      if (iConnectionTransactionIsolation != -1) {
        try {
          // if it is -1, then set the default
          //if (iConnectionTransactionIsolation == -1) {
          //  setInternalConnectionTransactionIsolation(null);
          //}
          // if the connections setting does not equal the pool's,
          // then set it (and log that somone changed it, but did not set it back
          if (c.getTransactionIsolation() != iConnectionTransactionIsolation) {
            // don't log changes to driver defaults if the connection
            // is brand new
            if (ch.numberOfOpens > 0) {
              logger.warn("[Pool@" +poolName +",id=" +id +"] setConnectionDefaults() : Checking transaction isolation level : Looks like someone has changed it from the default, and has not set it back. Default should be '" +getInternalConnectionTransactionIsolation() +"', but the connection value is '" +getInternalConnectionTransactionIsolation(c.getTransactionIsolation()) +"'");
            }
            c.setTransactionIsolation(iConnectionTransactionIsolation);
          }
        } catch (SQLException sqle) {
          logger.printStackTrace(sqle);
          ch.closeBehaviour = Pool.ON_CLOSE_SHOULD_DIE;
          ch.conn.close();
          connections.remove(ch);
          logger.warn("[Pool@" +poolName +",id=" +id +"] setConnectionDefaults() : Error checking transaction isolation level. Connection will be dumped.");
          notifyExceptionEvent();
          throw new CannotConnectException("Checking transaction isolation level value errored : " +sqle.toString(), sqle);
        }
      }

      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] setConnectionDefaults() leave");
  }


  /**
  *  Check if a connection is valid. This calls isClosed() (once we go to Java 6, we can call isValid())
  *  If user has configured "checkSQL" option, we run that also.
  *  If either checks fail, the connection is dumped.
  */
  private final boolean checkIfConnectionIsValid(Connection c, long id) {
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] checkIfConnectionIsValid() start");

    long now = System.currentTimeMillis();

    boolean checkret = false;

    try {
      checkret = c.isClosed();
      checkret = true;
    } catch (SQLException e) {
      logger.error("[Pool@" +poolName +",id=" +id +"] checkIfConnectionIsValid() : Error calling isClosed() on connection " +c +" : " +e);
    }


    // If its false (ie isClosed() returned false, then return false
    if (!checkret) return false;

    // Only run a check if they want it
    if (checkSQL != null && checkSQL.length() > 0) {
      logger.verbose("[Pool@" +poolName +",id=" +id +"] checkIfConnectionIsValid() : running checkSQL : " +checkSQL);
      checkret = ((PoolConnection)c).runCheckSQL(checkSQL);

    }
    logger.verbose("[Pool@" +poolName +",id=" +id +"] checkIfConnectionIsValid(" +checkret +") : took " +(System.currentTimeMillis() - now) +" ms");

    return checkret;
  }

  /**
   * Get a non pooled connection - ie just a dedicated connection
   * that someone can use once and once only.
   * So when the client calls close(), the connection dies off
   * @return a Connection object
   */
  private final Connection getNonPooledConnection(long id) throws PoolException {
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] getNonPooledConnection() start");

    ConnectionHolder ch = loadConnection(false, id);
    initializeConnection(ch, id);
    // override the close behaviour because its a non-pooled connection
    ch.closeBehaviour = ON_CLOSE_SHOULD_DIE;

    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] getNonPooledConnection() end : " +ch.conn);
    return ch.conn;
  }

  /**
   * Get a ConnectionHolder object, with the connection in it.
   *
   * @param addToList - should the ConnectionHolder be added to the list
   * of pooled connections
   * @return a ConnectionHolder object which contains the connection
   * @throws PoolException
   */
  private final ConnectionHolder loadConnection(boolean addToList, long id) throws PoolException {
    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName +",id=" +id +"] loadConnection() start");
    Connection raw = Util.getConnection(driverClass, driverURL, user, password);
    ConnectionHolder ch = new ConnectionHolder();
    PoolConnection pc = new PoolConnection(raw, ch);
    ch.conn = pc;
    ch.closeBehaviour = ON_CLOSE_SHOULD_REUSE;
    ch.status = CONNECTION_INACTIVE;
    ch.lock = lock;
    ch.poolName = poolName;
    ch.bDumpConnectionOnSQLException = this.bDumpConnectionOnSQLException;
    ch.myPool = this;
    ch.logger = this.logger;


    setConnectionDefaults(ch, id);

    if (addToList) {
      connections.addElement(ch);
    }
    return ch;
  }

  /**
  *  Close down all connection objects, and remove them from the connection list
  *  If force is true, then close the connection down immediately, else
  *   leave them to finish their job, and then close them. This means that pools can
  *  be stopped/started with no impact on live connections (ie safely).
  */
  public final void stop(boolean force) throws PoolException {
    if (bPoolHasBeenShutdown) {
      logger.warn("[Pool@" +poolName +"] stop() Pool has already been shutdown ... not doing it twice.");
      return;
    } 
    logger.email(Constants.STOP_EVENT, "Pool stopping at : " +new java.util.Date());

    logger.info("[Pool@" +poolName +"] stop() Stopping pool with force=" +force +" ...");
    // Lock access to the pool
    poolAccessLocked = true;

    // Kill the cutback failover thread if it exists
    if (failoverCutBackObj != null) {
      failoverCutBackObj.stopIt();
    }

    // Close down each connection
    for (ConnectionHolder ch : connections) {
      // If we are forcing a close, just dump it
      // Else, only dump if not busy
      // If they are busy, and we are not forcing close, then mark the connection
      // as not returning to the pool once its free (ie dump after work is done)
      try {
        if (force) {
          ch.conn.closePhysical();
        } else {
          if (ch.status == CONNECTION_INACTIVE) {
            ch.conn.closePhysical();
          } else {
            ch.closeBehaviour = ON_CLOSE_SHOULD_DIE;
          }
        }
      } catch (SQLException e) {
        logger.printStackTrace(e);
      }
    }

    connections.removeAllElements();

    logger.info("[Pool@" +poolName +"] stop() Shutting down pool monitor.");
    if (monitor != null) {
      monitor.shutdown();
    }


    logger.info("[Pool@" +poolName +"] stop() Stop Complete.");
    logger.close();
    bPoolHasBeenShutdown = true;
  }

  /**
  *  Start the pool, filling it with the configured base of connections
  */
  public final void start() throws PoolException {
    // set up the logger
    setUpLogger();
    gid = 0L;
    bPoolHasBeenShutdown = false;

    logger.verbose("[Pool@" +poolName +"] Checking " +poolName +" parameters ...");

    for (LoadRule rule : loadRules) {
      rule.runCheck(this, this.logger);
    }

    logger.email(Constants.START_EVENT, "Pool starting at : " +new java.util.Date());

    // print some verbose logging
    logger.info("[Pool@" +poolName +"] STARTING " +poolName +" ...");
    Util.printGetMethodValues("[Pool@" +poolName +"] config item : ", logger, PoolConfigImpl.class, this);

    // Lock access to the pool
    poolAccessLocked = true;

    // Make a new list
    connections = new Vector<ConnectionHolder>();
    numberOfWaitingThreads = 0;
    totalConnectionsHandedOut = 0;
    if (iNumberOfConnectionsToInitializeWith > iBase) {
      logger.warn("[Pool@" +poolName +"] start() The number of connections to initialise with is greater than the number of base connections ... adjusting init number to base : " +iBase);
      iNumberOfConnectionsToInitializeWith = iBase;
    }
    logger.verbose("[Pool@" +poolName +"] start() Loading " +iNumberOfConnectionsToInitializeWith +" Connection(s) on init");

    for (int i = 0; i < iNumberOfConnectionsToInitializeWith; i++) {
      try {
        Connection pc = loadConnection(true, i).conn;
        if (i == 0) {
          DatabaseMetaData conMD = pc.getMetaData();
          logger.info("[Pool@" +poolName +"] start() Primrose version " +Constants.VERSION +", release date " +Constants.RELEASE_DATE);
          logger.info("[Pool@" +poolName +"] start() JDBC Driver Name: " + conMD.getDriverName());
          logger.info("[Pool@" +poolName +"] start() JDBC Driver Version: " + conMD.getDriverVersion());
        }
      } catch (Throwable t){
        logger.warn("[Pool@" +poolName +"] start() Could not connect to db - is this OK ?");
        logger.printStackTrace(t);
      }
    }

    logger.info("[Pool@" +poolName +"] start() Starting new pool monitor.");
    monitor = new PoolMonitor(this, logger);
    monitor.start();


    logger.info("[Pool@" +poolName +"] start() Load complete.");


    // Unlock access to the pool - make it available
    poolAccessLocked = false;
  }

  public final Vector<ConnectionHolder> getPoolConnections() {
    return connections;
  }

  /**
  *  Restart the pool, calling stop(), then start()
  */
  public final void restart(boolean forceStop) throws PoolException {
    logger.info("[Pool@" +poolName +"] restart() Restarting pool ...");
    stop(forceStop);
    start();
    logger.info("[Pool@" +poolName +"] restart() Restart Complete ...");
  }

  public Logger getLogger() {
    return logger;
  }


  /**
   *   If a SQLException occurs and the parameter 'dumpConnectionOnSQLException' is true (default)
   *  then this method is called.
   *  If the config requires emails to be sent on SQLExceptions, then send it.
   *  If we require notification of a possible DB crash, then see if we have, using the
   *   'onExceptionCheckSQL' parameter SQL.
   *   If the config requires failover, then attempt that (if the db has crashed).
   */
  protected void notifyExceptionEvent() {
    if (emailEvents != null) {
      boolean notifyException = emailEvents.toUpperCase().indexOf(Constants.EXCEPTION_EVENT.toUpperCase()) > -1;
      boolean notifyCrash = emailEvents.toUpperCase().indexOf(Constants.DBCRASH_EVENT.toUpperCase()) > -1;

      if (notifyException) {
        logger.email(Constants.EXCEPTION_EVENT, "SQLException has occured in pool " +poolName);
      }

      if (notifyCrash) {
        boolean bHasCrashed = hasDbCrashed();

        if (notifyCrash && bHasCrashed) {
          logger.email(Constants.DBCRASH_EVENT, "Database seems to have crashed ! Driver URL : " +driverURL);
        }
        // If we don't want failover, or we already have failed over, then ignore
        if (failoverPool != null && failoverPoolObj == null) {
          attemptFailover();
        }
      }
    }

    // If we want failover, and we habe not already failed over, then
    // attempt a failover
    if (failoverPool != null && failoverPoolObj == null) {
      boolean bHasCrashed = hasDbCrashed();
      if (bHasCrashed) {
        attemptFailover();
      }
    }
  }

  /*
   * Work out if the db has crashed by running
   */
  private boolean hasDbCrashed() {
    boolean bHasCrashed = false;

    logger.verbose("[Pool@" +poolName +"] About to see if DB has crashed (get new connection & run onExceptionCheckSQL)...");
    try {
      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName+"] hasDbCrashed() Loading connection ...");
      //Throws exception if fails ( == yes, failover)
      ConnectionHolder ch = loadConnection(false, -7777);
      Connection c = ch.conn;
      // false == fail check, true == passed check
      // so bHasCrashed = !checkval
      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName+"] hasDbCrashed() Running onExceptionCheckSQL statement ...");
      bHasCrashed = ((PoolConnection)c).runCheckSQL(onExceptionCheckSQL);
      if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName+"] hasDbCrashed() runCheckSQL returned " +bHasCrashed);

      bHasCrashed = !bHasCrashed;

      try { if (c != null) c.close(); } catch (SQLException sqle) {}
    } catch (PoolException pe) {
      logger.printStackTrace(pe);
      bHasCrashed = true;
    }

    if (DebugLogger.getEnabled()) DebugLogger.log("[Pool@" +poolName+"] hasDbCrashed ? : " +bHasCrashed);
    return bHasCrashed;
  }

  /*
   * If failoverPool is set, and we have failed over, and then the DB is back up
   * then cutback to this pool.
   */
  public void cutbackFromFailoverPool() {
    logger.info("[Pool@" +poolName +"] Cutting back to this pool from failoverPool " +failoverPool);
    logger.email(Constants.CUTBACK_EVENT, "Cutting back to this pool from failoverPool " +failoverPool);
    try {
      stop(false);
      start();
      failoverPoolObj = null;
      logger.info("[Pool@" +poolName +"] Cutback to original pool succeeded");
      logger.email(Constants.CUTBACK_EVENT, "Cutback to original pool succeeded");
    } catch (PoolException pe) {
      logger.error("Cutback failed ...");
      logger.printStackTrace(pe);
      logger.email(Constants.CUTBACK_EVENT, "Cutback failed : " +pe);
    }
  }

  /**
   *   If a SQLException occurs and the parameter 'dumpConnectionOnSQLException' is true (default)
   *   and the parameter 'failoverPool' is set, then see if we should failover.
   */
  private void attemptFailover() {
    logger.info("[Pool@" +poolName +"] Now attemping failover to pool '" +failoverPool +"'");
    logger.email(Constants.FAILOVER_EVENT, "Now attemping failover to pool '" +failoverPool +"'");
    try {
      logger.info("[Pool@" +poolName +"] Finding failoverPool(" +failoverPool +")");
      // Restart and change the pool name
      failoverPoolObj = PoolLoader.findExistingPool(failoverPool);
      if (failoverPoolObj == null) {
        throw new PoolException("Cannot find failoverPool(" +failoverPool +") !");
      }
      logger.info("[Pool@" +poolName +"] Stopping this pool ...");
      stop(true);

      poolAccessLocked = false;
      logger.info("[Pool@" +poolName +"] Testing that can get connection from failoverPool ...");

      Connection c = getConnection();
      try { if (c != null) c.close(); } catch (SQLException sqle) {}

      logger.info("[Pool@" +poolName +"] Starting failover cutback monitor ...");
      failoverCutBackObj = new FailoverCutBack(this, logger);
      failoverCutBackObj.start();

      logger.info("[Pool@" +poolName +"] Failover complete. Routing all requests to " +poolName +" to " +failoverPool);
      logger.email(Constants.FAILOVER_EVENT, "Failover complete. Routing all requests to " +poolName +" to " +failoverPool);
    } catch (PoolException pe) {
      logger.error("[Pool@" +poolName +"] Failover failed : " +pe.toString());
      logger.email(Constants.FAILOVER_EVENT, "Failover failed : " +pe.toString());
      logger.printStackTrace(pe);
    }

  }


}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.