Logo Search packages:      
Sourcecode: mysql-connector-java version File versions  Download package

LoadBalancingConnectionProxy.java

/*
  Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.

  The MySQL Connector/J is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.

  This program is free software; you can redistribute it and/or modify it under the terms
  of the GNU General Public License as published by the Free Software Foundation; version 2
  of the License.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along with this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA
 
 */
package com.mysql.jdbc;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * An implementation of java.sql.Connection that load balances requests across a
 * series of MySQL JDBC connections, where the balancing takes place at
 * transaction commit.
 * 
 * Therefore, for this to work (at all), you must use transactions, even if only
 * reading data.
 * 
 * This implementation will invalidate connections that it detects have had
 * communication errors when processing a request. Problematic hosts will be
 * added to a global blacklist for loadBalanceBlacklistTimeout ms, after which
 * they will be removed from the blacklist and made eligible once again to be
 * selected for new connections.
 * 
 * This implementation is thread-safe, but it's questionable whether sharing a
 * connection instance amongst threads is a good idea, given that transactions
 * are scoped to connections in JDBC.
 * 
 * @version $Id: $
 * 
 */
00062 public class LoadBalancingConnectionProxy implements InvocationHandler,
            PingTarget {

      private static Method getLocalTimeMethod;
      
      private long totalPhysicalConnections = 0;
      private long activePhysicalConnections = 0;
      private String hostToRemove = null;
      private long lastUsed = 0;
      private long transactionCount = 0;
      private ConnectionGroup connectionGroup = null;
      private String closedReason = null;

      public static final String BLACKLIST_TIMEOUT_PROPERTY_KEY = "loadBalanceBlacklistTimeout";

      static {
            try {
                  getLocalTimeMethod = System.class.getMethod("nanoTime",
                              new Class[0]);
            } catch (SecurityException e) {
                  // ignore
            } catch (NoSuchMethodException e) {
                  // ignore
            }
      }

      // Lifted from C/J 5.1's JDBC-2.0 connection pool classes, let's merge this
      // if/when this gets into 5.1
00090       protected class ConnectionErrorFiringInvocationHandler implements
                  InvocationHandler {
            Object invokeOn = null;

            public ConnectionErrorFiringInvocationHandler(Object toInvokeOn) {
                  invokeOn = toInvokeOn;
            }

            public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                  Object result = null;

                  try {
                        result = method.invoke(invokeOn, args);

                        if (result != null) {
                              result = proxyIfInterfaceIsJdbc(result, result.getClass());
                        }
                  } catch (InvocationTargetException e) {
                        dealWithInvocationException(e);
                  }

                  return result;
            }
      }

      protected MySQLConnection currentConn;

      protected List<String> hostList;

      protected Map<String, ConnectionImpl> liveConnections;

      private Map<ConnectionImpl, String> connectionsToHostsMap;

      private long[] responseTimes;

      private Map<String, Integer> hostsToListIndexMap;

      private boolean inTransaction = false;

      private long transactionStartTime = 0;

      private Properties localProps;

      private boolean isClosed = false;

      private BalanceStrategy balancer;

      private int retriesAllDown;

      private static Map<String, Long> globalBlacklist = new HashMap<String, Long>();

      private int globalBlacklistTimeout = 0;
      
      private long connectionGroupProxyID = 0;

      private LoadBalanceExceptionChecker exceptionChecker;

      private Map<Class, Boolean> jdbcInterfacesForProxyCache = new HashMap<Class, Boolean>();
      
      private MySQLConnection thisAsConnection = null;

      private int autoCommitSwapThreshold = 0;
      
      private static Constructor JDBC_4_LB_CONNECTION_CTOR;
      
      static {
            if(Util.isJdbc4()){
                  try {
                        JDBC_4_LB_CONNECTION_CTOR =  Class.forName(
                                    "com.mysql.jdbc.JDBC4LoadBalancedMySQLConnection").getConstructor(
                                    new Class[] { LoadBalancingConnectionProxy.class});
                  } catch (SecurityException e) {
                        throw new RuntimeException(e);
                  } catch (NoSuchMethodException e) {
                        throw new RuntimeException(e);
                  } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                  }
            }
      }
      
      
      




      /**
       * Creates a proxy for java.sql.Connection that routes requests between the
       * given list of host:port and uses the given properties when creating
       * connections.
       * 
       * @param hosts
       * @param props
       * @throws SQLException
       */
00187       LoadBalancingConnectionProxy(List<String> hosts, Properties props)
                  throws SQLException {
            String group = props.getProperty("loadBalanceConnectionGroup",
                        null);
            boolean enableJMX = false;
            String enableJMXAsString = props.getProperty("loadBalanceEnableJMX",
                        "false");
            try{
                  enableJMX = Boolean.parseBoolean(enableJMXAsString);
            } catch (Exception e){
                  throw SQLError.createSQLException(Messages.getString(
                              "LoadBalancingConnectionProxy.badValueForLoadBalanceEnableJMX",
                              new Object[] { enableJMXAsString }),
                              SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);                 
            }
            
            if(group != null){
                  this.connectionGroup = ConnectionGroupManager.getConnectionGroupInstance(group);
                  if(enableJMX){
                        ConnectionGroupManager.registerJmx();
                  }
                  this.connectionGroupProxyID = this.connectionGroup.registerConnectionProxy(this, hosts);
                  hosts = new ArrayList<String>(this.connectionGroup.getInitialHosts());
            }
            
            this.hostList = hosts;

            int numHosts = this.hostList.size();

            this.liveConnections = new HashMap<String, ConnectionImpl>(numHosts);
            this.connectionsToHostsMap = new HashMap<ConnectionImpl, String>(numHosts);
            this.responseTimes = new long[numHosts];
            this.hostsToListIndexMap = new HashMap<String, Integer>(numHosts);

            this.localProps = (Properties) props.clone();
            this.localProps.remove(NonRegisteringDriver.HOST_PROPERTY_KEY);
            this.localProps.remove(NonRegisteringDriver.PORT_PROPERTY_KEY);

            for (int i = 0; i < numHosts; i++) {
                  this.hostsToListIndexMap.put(this.hostList.get(i), Integer.valueOf(i));
                  this.localProps.remove(NonRegisteringDriver.HOST_PROPERTY_KEY + "."
                              + (i + 1));
                  this.localProps.remove(NonRegisteringDriver.PORT_PROPERTY_KEY + "."
                              + (i + 1));
            }

            this.localProps.remove(NonRegisteringDriver.NUM_HOSTS_PROPERTY_KEY);
            this.localProps.setProperty("useLocalSessionState", "true");

            String strategy = this.localProps.getProperty("loadBalanceStrategy",
                        "random");
            
            String lbExceptionChecker = this.localProps.getProperty("loadBalanceExceptionChecker",
            "com.mysql.jdbc.StandardLoadBalanceExceptionChecker");


            String retriesAllDownAsString = this.localProps.getProperty(
                        "retriesAllDown", "120");

            try {
                  this.retriesAllDown = Integer.parseInt(retriesAllDownAsString);
            } catch (NumberFormatException nfe) {
                  throw SQLError.createSQLException(Messages.getString(
                              "LoadBalancingConnectionProxy.badValueForRetriesAllDown",
                              new Object[] { retriesAllDownAsString }),
                              SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
            }
            String blacklistTimeoutAsString = this.localProps.getProperty(
                        BLACKLIST_TIMEOUT_PROPERTY_KEY, "0");

            try {
                  this.globalBlacklistTimeout = Integer
                              .parseInt(blacklistTimeoutAsString);
            } catch (NumberFormatException nfe) {
                  throw SQLError
                              .createSQLException(
                                          Messages
                                                      .getString(
                                                                  "LoadBalancingConnectionProxy.badValueForLoadBalanceBlacklistTimeout",
                                                                  new Object[] { retriesAllDownAsString }),
                                          SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
            }

            if ("random".equals(strategy)) {
                  this.balancer = (BalanceStrategy) Util.loadExtensions(null, props,
                              "com.mysql.jdbc.RandomBalanceStrategy",
                              "InvalidLoadBalanceStrategy", null).get(0);
            } else if ("bestResponseTime".equals(strategy)) {
                  this.balancer = (BalanceStrategy) Util.loadExtensions(null, props,
                              "com.mysql.jdbc.BestResponseTimeBalanceStrategy",
                              "InvalidLoadBalanceStrategy", null).get(0);
            } else {
                  this.balancer = (BalanceStrategy) Util.loadExtensions(null, props,
                              strategy, "InvalidLoadBalanceStrategy", null).get(0);
            }

            String autoCommitSwapThresholdAsString = props.getProperty("loadBalanceAutoCommitStatementThreshold",
            "0");
            try {
                  this.autoCommitSwapThreshold  = Integer.parseInt(autoCommitSwapThresholdAsString);
            } catch (NumberFormatException nfe) {
                  throw SQLError.createSQLException(Messages.getString(
                              "LoadBalancingConnectionProxy.badValueForLoadBalanceAutoCommitStatementThreshold",
                              new Object[] { autoCommitSwapThresholdAsString }),
                              SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
            }
            
            String autoCommitSwapRegex = props.getProperty("loadBalanceAutoCommitStatementRegex","");
            if(!("".equals(autoCommitSwapRegex))){
                  try{
                        "".matches(autoCommitSwapRegex);
                  } catch (Exception e){
                        throw SQLError.createSQLException(Messages.getString(
                                    "LoadBalancingConnectionProxy.badValueForLoadBalanceAutoCommitStatementRegex",
                                    new Object[] { autoCommitSwapRegex }),
                                    SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null);
                  }
            }
            
            if(this.autoCommitSwapThreshold > 0){
                  String statementInterceptors = this.localProps.getProperty("statementInterceptors");
                  if(statementInterceptors == null){
                        this.localProps.setProperty("statementInterceptors", "com.mysql.jdbc.LoadBalancedAutoCommitInterceptor");
                  } else if(statementInterceptors.length() > 0){
                        this.localProps.setProperty("statementInterceptors", statementInterceptors + ",com.mysql.jdbc.LoadBalancedAutoCommitInterceptor");
                  }
                  props.setProperty("statementInterceptors", this.localProps.getProperty("statementInterceptors"));
                  
            }
            this.balancer.init(null, props);
            

            this.exceptionChecker = (LoadBalanceExceptionChecker) Util.loadExtensions(null, props,
                        lbExceptionChecker, "InvalidLoadBalanceExceptionChecker", null).get(0);
            this.exceptionChecker.init(null, props);

            if(Util.isJdbc4()  || JDBC_4_LB_CONNECTION_CTOR != null){
                  thisAsConnection =  (MySQLConnection) Util.handleNewInstance(JDBC_4_LB_CONNECTION_CTOR,
                              new Object[] {this}, null);
            }else{
                  thisAsConnection = new LoadBalancedMySQLConnection(this);
            }
            pickNewConnection();
            
            
      }

      /**
       * Creates a new physical connection for the given host:port and updates
       * required internal mappings and statistics for that connection.
       * 
       * @param hostPortSpec
       * @return
       * @throws SQLException
       */
00342       public synchronized ConnectionImpl createConnectionForHost(
                  String hostPortSpec) throws SQLException {
            Properties connProps = (Properties) this.localProps.clone();

            String[] hostPortPair = NonRegisteringDriver
                        .parseHostPortPair(hostPortSpec);
            String hostName = hostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];
            String portNumber = hostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX];
            String dbName = connProps
                        .getProperty(NonRegisteringDriver.DBNAME_PROPERTY_KEY);

            if (hostName == null) {
                  throw new SQLException(
                              "Could not find a hostname to start a connection to");
            }
            if (portNumber == null) {
                  portNumber = "3306";// use default
            }

            connProps.setProperty(NonRegisteringDriver.HOST_PROPERTY_KEY, hostName);
            connProps.setProperty(NonRegisteringDriver.PORT_PROPERTY_KEY,
                        portNumber);
            connProps.setProperty(NonRegisteringDriver.HOST_PROPERTY_KEY + ".1",
                        hostName);
            connProps.setProperty(NonRegisteringDriver.PORT_PROPERTY_KEY + ".1",
                        portNumber);
            connProps.setProperty(NonRegisteringDriver.NUM_HOSTS_PROPERTY_KEY, "1");
            connProps.setProperty("roundRobinLoadBalance", "false"); // make sure we
                                                                                                      // don't
                                                                                                      // pickup
                                                                                                      // the
                                                                                                      // default
                                                                                                      // value

            ConnectionImpl conn = (ConnectionImpl) ConnectionImpl.getInstance(
                        hostName, Integer.parseInt(portNumber), connProps, dbName,
                        "jdbc:mysql://" + hostName + ":" + portNumber + "/");
            
            

            this.liveConnections.put(hostPortSpec, conn);
            this.connectionsToHostsMap.put(conn, hostPortSpec);
            

            this.activePhysicalConnections++;
            this.totalPhysicalConnections++;

            conn.setProxy(this.thisAsConnection);

            return conn;
      }

      /**
       * @param e
       * @throws SQLException
       * @throws Throwable
       * @throws InvocationTargetException
       */
00400       void dealWithInvocationException(InvocationTargetException e)
                  throws SQLException, Throwable, InvocationTargetException {
            Throwable t = e.getTargetException();

            if (t != null) {
                  if (t instanceof SQLException && shouldExceptionTriggerFailover((SQLException) t )) {
                        invalidateCurrentConnection();
                        pickNewConnection();
                  }

                  throw t;
            }

            throw e;
      }

      /**
       * Closes current connection and removes it from required mappings.
       * 
       * @throws SQLException
       */
00421       synchronized void invalidateCurrentConnection() throws SQLException {
            try {
                  if (!this.currentConn.isClosed()) {
                        this.currentConn.close();
                  }

            } finally {
                  // add host to the global blacklist, if enabled
                  if (this.isGlobalBlacklistEnabled()) {
                        this.addToGlobalBlacklist(this.connectionsToHostsMap
                                    .get(this.currentConn));

                  }
                  // remove from liveConnections
                  this.liveConnections.remove(this.connectionsToHostsMap
                              .get(this.currentConn));
                  Object mappedHost = this.connectionsToHostsMap
                              .remove(this.currentConn);
                  if (mappedHost != null
                              && this.hostsToListIndexMap.containsKey(mappedHost)) {
                        int hostIndex = (this.hostsToListIndexMap
                                    .get(mappedHost)).intValue();
                        // reset the statistics for the host
                        synchronized (this.responseTimes) {
                              this.responseTimes[hostIndex] = 0;
                        }
                  }
            }
      }

      private void closeAllConnections() {
            synchronized (this) {
                  // close all underlying connections
                  Iterator<ConnectionImpl> allConnections = this.liveConnections.values().iterator();

                  while (allConnections.hasNext()) {
                        try {
                              this.activePhysicalConnections--;
                              allConnections.next().close();
                        } catch (SQLException e) {
                        }
                  }

                  if (!this.isClosed) {
                        this.balancer.destroy();
                        if(this.connectionGroup != null){
                              this.connectionGroup.closeConnectionProxy(this);
                        }
                  }

                  this.liveConnections.clear();
                  this.connectionsToHostsMap.clear();
            }

      }
      
            
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return this.invoke(proxy, method, args, true);
      }

      /**
       * Proxies method invocation on the java.sql.Connection interface, trapping
       * "close", "isClosed" and "commit/rollback" (to switch connections for load
       * balancing).
       */
00487       public synchronized Object invoke(Object proxy, Method method, Object[] args, boolean swapAtTransactionBoundary)
                  throws Throwable {

            String methodName = method.getName();
            
            if("getLoadBalanceSafeProxy".equals(methodName)){
                  return this.currentConn;
            }

            if ("equals".equals(methodName) && args.length == 1) {
                  if (args[0] instanceof Proxy) {
                        return Boolean.valueOf((((Proxy) args[0]).equals(this)));
                  }
                  return Boolean.valueOf(this.equals(args[0]));
            }

            if ("hashCode".equals(methodName)) {
                  return Integer.valueOf(this.hashCode());
            }

            if ("close".equals(methodName)) {
                  closeAllConnections();

                  this.isClosed = true;
                  this.closedReason = "Connection explicitly closed.";

                  return null;
            }

            if ("isClosed".equals(methodName)) {
                  return Boolean.valueOf(this.isClosed);
            }

            if (this.isClosed) {
                  String reason = "No operations allowed after connection closed.";
                  if(this.closedReason != null){
                        reason += ("  " + this.closedReason);
                  }
                  throw SQLError.createSQLException(
                              reason,
                              SQLError.SQL_STATE_CONNECTION_NOT_OPEN, null /*
                                                                                                 * no access to
                                                                                                 * a interceptor
                                                                                                 * here...
                                                                                                 */);
            }

            if (!inTransaction) {
                  this.inTransaction = true;
                  this.transactionStartTime = getLocalTimeBestResolution();
                  this.transactionCount++;
            }

            Object result = null;

            try {
                  this.lastUsed = System.currentTimeMillis();
                  result = method.invoke(thisAsConnection, args);

                  if (result != null) {
                        if (result instanceof com.mysql.jdbc.Statement) {
                              ((com.mysql.jdbc.Statement) result).setPingTarget(this);
                        }

                        result = proxyIfInterfaceIsJdbc(result, result.getClass());
                  }
            } catch (InvocationTargetException e) {
                  dealWithInvocationException(e);
            } finally {
                  if (swapAtTransactionBoundary && ("commit".equals(methodName) || "rollback".equals(methodName))) {
                        this.inTransaction = false;

                        // Update stats
                        String host = this.connectionsToHostsMap.get(this.currentConn);
                        // avoid NPE if the connection has already been removed from
                        // connectionsToHostsMap
                        // in invalidateCurrenctConnection()
                        if (host != null) {
                              synchronized (this.responseTimes) {
                                    int hostIndex = (this.hostsToListIndexMap
                                                .get(host)).intValue();

                                    if(hostIndex < this.responseTimes.length){
                                          this.responseTimes[hostIndex] = getLocalTimeBestResolution()
                                                      - this.transactionStartTime;
                                    }
                              }
                        }
                        pickNewConnection();
                  }
            }

            return result;
      }

      /**
       * Picks the "best" connection to use for the next transaction based on the
       * BalanceStrategy in use.
       * 
       * @throws SQLException
       */
00588       protected synchronized void pickNewConnection() throws SQLException {
            if (this.currentConn == null) { // startup
                  this.currentConn = this.balancer.pickConnection(this, Collections
                              .unmodifiableList(this.hostList), Collections
                              .unmodifiableMap(this.liveConnections),
                              this.responseTimes.clone(), this.retriesAllDown);
                  return;
            }
            
            if(this.currentConn.isClosed()){
                  invalidateCurrentConnection();
            }

            int pingTimeout = this.currentConn.getLoadBalancePingTimeout();
            boolean pingBeforeReturn = this.currentConn.getLoadBalanceValidateConnectionOnSwapServer();
            
            for(int hostsTried = 0, hostsToTry = this.hostList.size(); hostsTried <= hostsToTry; hostsTried++){
                  try{
                        ConnectionImpl newConn = this.balancer.pickConnection(

                              this, Collections.unmodifiableList(this.hostList), Collections
                                          .unmodifiableMap(this.liveConnections),
                              this.responseTimes.clone(), this.retriesAllDown);
            
                        if (this.currentConn != null) {
                              if(pingBeforeReturn){
                                    if(pingTimeout == 0){
                                          newConn.ping();
                                    } else {
                                          newConn.pingInternal(true, pingTimeout);
                                    }
                              }
                              
                              syncSessionState(this.currentConn, newConn);
                        }
                        
                        this.currentConn = newConn;
                        return;
                  } catch (SQLException e){
                        
                        if (shouldExceptionTriggerFailover(e)) {
                              // connection error, close up shop on current
                              // connection
                              invalidateCurrentConnection();
                        }
                  }

            }
            // no hosts available to swap connection to, close up.
            this.isClosed = true;
            this.closedReason = "Connection closed after inability to pick valid new connection during fail-over.";

      }

      /**
       * Recursively checks for interfaces on the given object to determine if it
       * implements a java.sql interface, and if so, proxies the instance so that
       * we can catch and fire SQL errors.
       * 
       * @param toProxy
       * @param clazz
       * @return
       */
00651       Object proxyIfInterfaceIsJdbc(Object toProxy, Class clazz) {
            
            if(isInterfaceJdbc(clazz)){
                  
            Class[] interfacesToProxy = getAllInterfacesToProxy(clazz);
            
            return Proxy.newProxyInstance(toProxy.getClass()
                        .getClassLoader(), interfacesToProxy,
                        createConnectionProxy(toProxy));
            }

            return toProxy;
      }

      private Map<Class, Class[]> allInterfacesToProxy = new HashMap<Class, Class[]>();
      
      private Class[] getAllInterfacesToProxy(Class clazz) {
            Class[] interfacesToProxy = (Class[]) this.allInterfacesToProxy.get(clazz);
            
            if (interfacesToProxy != null) {
                  return interfacesToProxy;
            }
            
            List<Class> interfaces = new LinkedList<Class>();
            
            Class superClass = clazz;
            
            while (!(superClass.equals(Object.class))) {
                  Class[] declared = superClass.getInterfaces();
                  
                  for (int i = 0; i < declared.length; i++) {
                        interfaces.add(declared[i]);
                  }
                  
                  superClass = superClass.getSuperclass();
            }
            
            interfacesToProxy = new Class[interfaces.size()];
            interfaces.toArray(interfacesToProxy);
            
            this.allInterfacesToProxy.put(clazz, interfacesToProxy);
            
            return interfacesToProxy;
      }
      
      
      private boolean isInterfaceJdbc(Class clazz){
            if(this.jdbcInterfacesForProxyCache.containsKey(clazz)){
                  return (this.jdbcInterfacesForProxyCache.get(clazz)).booleanValue();
            }
            
            Class[] interfaces = clazz.getInterfaces();

            for (int i = 0; i < interfaces.length; i++) {
                  String packageName = interfaces[i].getPackage().getName();

                  if ("java.sql".equals(packageName)
                              || "javax.sql".equals(packageName)
                              || "com.mysql.jdbc".equals(packageName)) {
                        this.jdbcInterfacesForProxyCache.put(clazz, Boolean.valueOf(true));
                        
                        return true;
                  }

                  if(isInterfaceJdbc(interfaces[i])){
                        this.jdbcInterfacesForProxyCache.put(clazz, Boolean.valueOf(true));
                        
                        return true;
                  }
            }

            this.jdbcInterfacesForProxyCache.put(clazz, Boolean.valueOf(false));
            return false;
            
      }

      protected ConnectionErrorFiringInvocationHandler createConnectionProxy(
                  Object toProxy) {
            return new ConnectionErrorFiringInvocationHandler(toProxy);
      }

      /**
       * Returns best-resolution representation of local time, using nanoTime() if
       * available, otherwise defaulting to currentTimeMillis().
       */
00736       private static long getLocalTimeBestResolution() {
            if (getLocalTimeMethod != null) {
                  try {
                        return ((Long) getLocalTimeMethod.invoke(null, (Object[])null))
                                    .longValue();
                  } catch (IllegalArgumentException e) {
                        // ignore - we fall through to currentTimeMillis()
                  } catch (IllegalAccessException e) {
                        // ignore - we fall through to currentTimeMillis()
                  } catch (InvocationTargetException e) {
                        // ignore - we fall through to currentTimeMillis()
                  }
            }

            return System.currentTimeMillis();
      }

      public synchronized void doPing() throws SQLException {
            SQLException se = null;
            boolean foundHost = false;
            int pingTimeout = this.currentConn.getLoadBalancePingTimeout();
            synchronized (this) {
                  for (Iterator<String> i = this.hostList.iterator(); i.hasNext();) {
                        String host = i.next();
                        ConnectionImpl conn = this.liveConnections.get(host);
                        if (conn == null) {
                              continue;
                        }
                        try {
                              if(pingTimeout == 0){
                                    conn.ping();
                              } else {
                                    conn.pingInternal(true, pingTimeout);
                              }
                              foundHost = true;
                        } catch (SQLException e) {
                              this.activePhysicalConnections--;
                              // give up if it is the current connection, otherwise NPE
                              // faking resultset later.
                              if (host.equals(this.connectionsToHostsMap
                                          .get(this.currentConn))) {
                                    // clean up underlying connections, since connection
                                    // pool won't do it
                                    closeAllConnections();
                                    this.isClosed = true;
                                    this.closedReason = "Connection closed because ping of current connection failed.";
                                    throw e;
                              }

                              // if the Exception is caused by ping connection lifetime
                              // checks, don't add to blacklist
                              if (e
                                          .getMessage()
                                          .equals(
                                                      Messages
                                                                  .getString("Connection.exceededConnectionLifetime"))) {
                                    // only set the return Exception if it's null
                                    if (se == null) {
                                          se = e;
                                    }
                              } else {
                                    // overwrite the return Exception no matter what
                                    se = e;
                                    if (this.isGlobalBlacklistEnabled()) {
                                          this.addToGlobalBlacklist(host);
                                    }
                              }
                              // take the connection out of the liveConnections Map
                              this.liveConnections.remove(this.connectionsToHostsMap
                                          .get(conn));
                        }
                  }
            }
            // if there were no successful pings
            if (!foundHost) {
                  closeAllConnections();
                  this.isClosed = true;
                  this.closedReason = "Connection closed due to inability to ping any active connections.";
                  // throw the stored Exception, if exists
                  if (se != null) {
                        throw se;
                  }
                  // or create a new SQLException and throw it, must be no
                  // liveConnections
                  ((ConnectionImpl) this.currentConn)
                              .throwConnectionClosedException();
            }
      }

      public void addToGlobalBlacklist(String host, long timeout) {
            if (this.isGlobalBlacklistEnabled()) {
                  synchronized (globalBlacklist) {
                        globalBlacklist.put(host, Long.valueOf(timeout));
                  }
            }
      }
      
      public void addToGlobalBlacklist(String host){
            addToGlobalBlacklist(host, System.currentTimeMillis()
                                    + this.globalBlacklistTimeout);
            
      }

      public boolean isGlobalBlacklistEnabled() {
            return (this.globalBlacklistTimeout > 0);
      }

      public synchronized Map<String, Long> getGlobalBlacklist() {
            if (!this.isGlobalBlacklistEnabled()) {
                  String localHostToRemove = this.hostToRemove;
                  
                  if(hostToRemove != null){
                        HashMap<String, Long> fakedBlacklist = new HashMap<String, Long>();
                        fakedBlacklist.put(localHostToRemove, Long.valueOf(System.currentTimeMillis() + 5000));
                        return fakedBlacklist;
                  }
                  
                  return new HashMap<String, Long>(1);
            }

            // Make a local copy of the blacklist
            Map<String, Long> blacklistClone = new HashMap<String, Long>(globalBlacklist.size());
            // Copy everything from synchronized global blacklist to local copy for
            // manipulation
            synchronized (globalBlacklist) {
                  blacklistClone.putAll(globalBlacklist);
            }
            Set<String> keys = blacklistClone.keySet();

            // we're only interested in blacklisted hosts that are in the hostList
            keys.retainAll(this.hostList);
            if (keys.size() == this.hostList.size()) {
                  // return an empty blacklist, let the BalanceStrategy
                  // implementations try to connect to everything
                  // since it appears that all hosts are unavailable - we don't want
                  // to wait for
                  // loadBalanceBlacklistTimeout to expire.
                  return new HashMap<String, Long>(1);
            }

            // Don't need to synchronize here as we using a local copy
            for (Iterator<String> i = keys.iterator(); i.hasNext();) {
                  String host = i.next();
                  // OK if null is returned because another thread already purged Map
                  // entry.
                  Long timeout = globalBlacklist.get(host);
                  if (timeout != null
                              && timeout.longValue() < System.currentTimeMillis()) {
                        // Timeout has expired, remove from blacklist
                        synchronized (globalBlacklist) {
                              globalBlacklist.remove(host);
                        }
                        i.remove();
                  }

            }
            
            return blacklistClone;
      }

      public boolean shouldExceptionTriggerFailover(SQLException ex){
            return this.exceptionChecker.shouldExceptionTriggerFailover(ex);
            
      }
      
      public void removeHostWhenNotInUse(String host)
                  throws SQLException {
            int timeBetweenChecks = 1000;
            long timeBeforeHardFail = 15000;
            
            synchronized (this) {
                  addToGlobalBlacklist(host, timeBeforeHardFail + 1000);
                  
                  long cur = System.currentTimeMillis();
                  
                  while (System.currentTimeMillis() - timeBeforeHardFail < cur) {
      
                        this.hostToRemove = host;
                        
                        if (!host.equals(this.currentConn.getHost())) {
                              removeHost(host);
                              return;
                        }
                  }
            }
            
            try {
                  Thread.sleep(timeBetweenChecks);
            } catch (InterruptedException e) {
                  // better to swallow this and retry.
            }

            removeHost(host);
      }
      
      public synchronized void removeHost(String host) throws SQLException {

            if (this.connectionGroup != null) {
                  if (this.connectionGroup.getInitialHosts().size() == 1
                              && this.connectionGroup.getInitialHosts().contains(host)) {
                        throw SQLError.createSQLException(
                                    "Cannot remove only configured host.", null);
                  }

                  this.hostToRemove = host;
                  
                  if (host.equals(this.currentConn.getHost())) {
                        closeAllConnections();
                  } else {
                        this.connectionsToHostsMap.remove(this.liveConnections
                                    .remove(host));
                        Integer idx = this.hostsToListIndexMap.remove(host);
                        long[] newResponseTimes = new long[this.responseTimes.length - 1];
                        int newIdx = 0;
                        for (Iterator<String> i = this.hostList.iterator(); i.hasNext(); newIdx++) {
                              String copyHost = i.next();
                              if (idx != null
                                          && idx.intValue() < this.responseTimes.length) {
                                    newResponseTimes[newIdx] = this.responseTimes[idx
                                                .intValue()];
                                    this.hostsToListIndexMap.put(copyHost,
                                                Integer.valueOf(newIdx));
                              }
                        }
                        this.responseTimes = newResponseTimes;
                  }
            }

      }

      public synchronized boolean addHost(String host) {

            if (this.hostsToListIndexMap.containsKey(host)) {
                  return false;
            }
            
            long[] newResponseTimes = new long[this.responseTimes.length + 1];
            
            for (int i = 0; i < this.responseTimes.length; i++) {
                  newResponseTimes[i] = this.responseTimes[i];
            }
            
            this.responseTimes = newResponseTimes;
            this.hostList.add(host);
            this.hostsToListIndexMap.put(host,
                        Integer.valueOf(this.responseTimes.length - 1));
            
            return true;
      }
      
      public synchronized long getLastUsed(){
            return this.lastUsed;
      }
      
      public synchronized boolean inTransaction(){
            return this.inTransaction;
      }
      
      public synchronized long getTransactionCount(){
            return this.transactionCount;
      }
      
      public synchronized long getActivePhysicalConnectionCount(){
            return this.activePhysicalConnections;
      }
      
      public synchronized long getTotalPhysicalConnectionCount(){
            return this.totalPhysicalConnections;
      }
      
      public synchronized long getConnectionGroupProxyID(){
            return this.connectionGroupProxyID;
      }
      
      public synchronized String getCurrentActiveHost() {
            MySQLConnection c = this.currentConn;
            if(c != null){
                  Object o = this.connectionsToHostsMap.get(c);
                  if(o != null){
                        return o.toString();
                  }
            }
            return null;
      }
      
      public synchronized long getCurrentTransactionDuration(){

            if (this.inTransaction && this.transactionStartTime > 0) {
                  return getLocalTimeBestResolution() - this.transactionStartTime;
            }
            
            return 0;
      }
      
      protected void syncSessionState(Connection initial, Connection target)
                  throws SQLException {
            if (initial == null || target == null) {
                  return;
            }
            target.setAutoCommit(initial.getAutoCommit());
            target.setCatalog(initial.getCatalog());
            target.setTransactionIsolation(initial.getTransactionIsolation());
            target.setReadOnly(initial.isReadOnly());

      }

}

Generated by  Doxygen 1.6.0   Back to index