/** * Parses hostPortPair in the form of [host][:port] into an array, with the element of index * HOST_NAME_INDEX being the host (or null if not specified), and the element of index * PORT_NUMBER_INDEX being the port (or null if not specified). * * @param hostPortPair host and port in form of of [host][:port] * @return array containing host and port as Strings * @throws SQLException if a parse error occurs */ protected static String[] parseHostPortPair(String hostPortPair) throws SQLException { int portIndex = hostPortPair.indexOf(":"); // $NON-NLS-1$ String[] splitValues = new String[2]; String hostname = null; if (portIndex != -1) { if ((portIndex + 1) < hostPortPair.length()) { String portAsString = hostPortPair.substring(portIndex + 1); hostname = hostPortPair.substring(0, portIndex); splitValues[HOST_NAME_INDEX] = hostname; splitValues[PORT_NUMBER_INDEX] = portAsString; } else { throw SQLError.createSQLException( Messages.getString("NonRegisteringDriver.37"), // $NON-NLS-1$ SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); } } else { splitValues[HOST_NAME_INDEX] = hostPortPair; splitValues[PORT_NUMBER_INDEX] = null; } return splitValues; }
/** * Try to make a database connection to the given URL. The driver should return "null" if it * realizes it is the wrong kind of driver to connect to the given URL. This will be common, as * when the JDBC driverManager is asked to connect to a given URL, it passes the URL to each * loaded driver in turn. * * <p>The driver should raise an SQLException if it is the right driver to connect to the given * URL, but has trouble connecting to the database. * * <p>The java.util.Properties argument can be used to pass arbitrary string tag/value pairs as * connection arguments. * * <p>My protocol takes the form: * * <PRE> * * jdbc:mysql://host:port/database * * </PRE> * * @param url the URL of the database to connect to * @param info a list of arbitrary tag/value pairs as connection arguments * @return a connection to the URL or null if it isnt us * @exception SQLException if a database access error occurs * @see java.sql.Driver#connect */ public java.sql.Connection connect(String url, Properties info) throws SQLException { if (url != null) { if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) { return connectLoadBalanced(url, info); } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) { return connectReplicationConnection(url, info); } } Properties props = null; if ((props = parseURL(url, info)) == null) { return null; } try { Connection newConn = new com.mysql.jdbc.Connection(host(props), port(props), props, database(props), url); return newConn; } catch (SQLException sqlEx) { // Don't wrap SQLExceptions, throw // them un-changed. throw sqlEx; } catch (Exception ex) { throw SQLError.createSQLException( Messages.getString("NonRegisteringDriver.17") // $NON-NLS-1$ + ex.toString() + Messages.getString("NonRegisteringDriver.18"), // $NON-NLS-1$ SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE); } }
/** * Removes a host from the host list. * * @param host The host to be removed. * @throws SQLException */ 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.currentConnection.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 < this.responseTimes.length) { newResponseTimes[newIdx] = this.responseTimes[idx]; this.hostsToListIndexMap.put(copyHost, newIdx); } } this.responseTimes = newResponseTimes; } } }
/** * 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 */ LoadBalancingConnectionProxy(List hosts, Properties props) throws SQLException { this.hostList = hosts; int numHosts = this.hostList.size(); this.liveConnections = new HashMap(numHosts); this.connectionsToHostsMap = new HashMap(numHosts); this.responseTimes = new long[numHosts]; this.hostsToListIndexMap = new HashMap(numHosts); for (int i = 0; i < numHosts; i++) { this.hostsToListIndexMap.put(this.hostList.get(i), new Integer(i)); } this.localProps = (Properties) props.clone(); this.localProps.remove(NonRegisteringDriver.HOST_PROPERTY_KEY); this.localProps.remove(NonRegisteringDriver.PORT_PROPERTY_KEY); this.localProps.setProperty("useLocalSessionState", "true"); String strategy = this.localProps.getProperty("loadBalanceStrategy", "random"); if ("random".equals(strategy)) { this.balancer = new RandomBalanceStrategy(); } else if ("bestResponseTime".equals(strategy)) { this.balancer = new BestResponseTimeBalanceStrategy(); } else { throw SQLError.createSQLException( Messages.getString("InvalidLoadBalanceStrategy", new Object[] {strategy}), SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } pickNewConnection(); }
public Properties parseURL(String url, Properties defaults) throws java.sql.SQLException { Properties urlProps = (defaults != null) ? new Properties(defaults) : new Properties(); if (url == null) { return null; } if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) { // $NON-NLS-1$ return null; } int beginningOfSlashes = url.indexOf("//"); if (StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)) { urlProps.setProperty( "socketFactory", "com.mysql.management.driverlaunched.ServerLauncherSocketFactory"); } /* * Parse parameters after the ? in the URL and remove them from the * original URL. */ int index = url.indexOf("?"); // $NON-NLS-1$ if (index != -1) { String paramString = url.substring(index + 1, url.length()); url = url.substring(0, index); StringTokenizer queryParams = new StringTokenizer(paramString, "&"); // $NON-NLS-1$ while (queryParams.hasMoreTokens()) { String parameterValuePair = queryParams.nextToken(); int indexOfEquals = StringUtils.indexOfIgnoreCase(0, parameterValuePair, "="); String parameter = null; String value = null; if (indexOfEquals != -1) { parameter = parameterValuePair.substring(0, indexOfEquals); if (indexOfEquals + 1 < parameterValuePair.length()) { value = parameterValuePair.substring(indexOfEquals + 1); } } if ((value != null && value.length() > 0) && (parameter != null && parameter.length() > 0)) { try { urlProps.put(parameter, URLDecoder.decode(value, "UTF-8")); } catch (UnsupportedEncodingException badEncoding) { // punt urlProps.put(parameter, URLDecoder.decode(value)); } catch (NoSuchMethodError nsme) { // punt again urlProps.put(parameter, URLDecoder.decode(value)); } } } } url = url.substring(beginningOfSlashes + 2); String hostStuff = null; int slashIndex = url.indexOf("/"); // $NON-NLS-1$ if (slashIndex != -1) { hostStuff = url.substring(0, slashIndex); if ((slashIndex + 1) < url.length()) { urlProps.put( DBNAME_PROPERTY_KEY, //$NON-NLS-1$ url.substring((slashIndex + 1), url.length())); } } else { hostStuff = url; } if ((hostStuff != null) && (hostStuff.length() > 0)) { urlProps.put(HOST_PROPERTY_KEY, hostStuff); // $NON-NLS-1$ } String propertiesTransformClassName = urlProps.getProperty(PROPERTIES_TRANSFORM_KEY); if (propertiesTransformClassName != null) { try { ConnectionPropertiesTransform propTransformer = (ConnectionPropertiesTransform) Class.forName(propertiesTransformClassName).newInstance(); urlProps = propTransformer.transformProperties(urlProps); } catch (InstantiationException e) { throw SQLError.createSQLException( "Unable to create properties transform instance '" + propertiesTransformClassName + "' due to underlying exception: " + e.toString(), SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); } catch (IllegalAccessException e) { throw SQLError.createSQLException( "Unable to create properties transform instance '" + propertiesTransformClassName + "' due to underlying exception: " + e.toString(), SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); } catch (ClassNotFoundException e) { throw SQLError.createSQLException( "Unable to create properties transform instance '" + propertiesTransformClassName + "' due to underlying exception: " + e.toString(), SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); } } if (Util.isColdFusion() && urlProps.getProperty("autoConfigureForColdFusion", "true").equalsIgnoreCase("true")) { String configs = urlProps.getProperty(USE_CONFIG_PROPERTY_KEY); StringBuffer newConfigs = new StringBuffer(); if (configs != null) { newConfigs.append(configs); newConfigs.append(","); } newConfigs.append("coldFusion"); urlProps.setProperty(USE_CONFIG_PROPERTY_KEY, newConfigs.toString()); } // If we use a config, it actually should get overridden by anything in // the URL or passed-in properties String configNames = null; if (defaults != null) { configNames = defaults.getProperty(USE_CONFIG_PROPERTY_KEY); } if (configNames == null) { configNames = urlProps.getProperty(USE_CONFIG_PROPERTY_KEY); } if (configNames != null) { List splitNames = StringUtils.split(configNames, ",", true); Properties configProps = new Properties(); Iterator namesIter = splitNames.iterator(); while (namesIter.hasNext()) { String configName = (String) namesIter.next(); try { InputStream configAsStream = getClass().getResourceAsStream("configs/" + configName + ".properties"); if (configAsStream == null) { throw SQLError.createSQLException( "Can't find configuration template named '" + configName + "'", SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); } configProps.load(configAsStream); } catch (IOException ioEx) { throw SQLError.createSQLException( "Unable to load configuration template '" + configName + "' due to underlying IOException: " + ioEx, SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); } } Iterator propsIter = urlProps.keySet().iterator(); while (propsIter.hasNext()) { String key = propsIter.next().toString(); String property = urlProps.getProperty(key); configProps.setProperty(key, property); } urlProps = configProps; } // Properties passed in should override ones in URL if (defaults != null) { Iterator propsIter = defaults.keySet().iterator(); while (propsIter.hasNext()) { String key = propsIter.next().toString(); String property = defaults.getProperty(key); urlProps.setProperty(key, property); } } return urlProps; }
private java.sql.Connection connectReplicationConnection(String url, Properties info) throws SQLException { Properties parsedProps = parseURL(url, info); if (parsedProps == null) { return null; } Properties masterProps = (Properties) parsedProps.clone(); Properties slavesProps = (Properties) parsedProps.clone(); // Marker used for further testing later on, also when // debugging slavesProps.setProperty("com.mysql.jdbc.ReplicationConnection.isSlave", "true"); String hostValues = parsedProps.getProperty(HOST_PROPERTY_KEY); if (hostValues != null) { StringTokenizer st = new StringTokenizer(hostValues, ","); StringBuffer masterHost = new StringBuffer(); StringBuffer slaveHosts = new StringBuffer(); if (st.hasMoreTokens()) { String[] hostPortPair = parseHostPortPair(st.nextToken()); if (hostPortPair[HOST_NAME_INDEX] != null) { masterHost.append(hostPortPair[HOST_NAME_INDEX]); } if (hostPortPair[PORT_NUMBER_INDEX] != null) { masterHost.append(":"); masterHost.append(hostPortPair[PORT_NUMBER_INDEX]); } } boolean firstSlaveHost = true; while (st.hasMoreTokens()) { String[] hostPortPair = parseHostPortPair(st.nextToken()); if (!firstSlaveHost) { slaveHosts.append(","); } else { firstSlaveHost = false; } if (hostPortPair[HOST_NAME_INDEX] != null) { slaveHosts.append(hostPortPair[HOST_NAME_INDEX]); } if (hostPortPair[PORT_NUMBER_INDEX] != null) { slaveHosts.append(":"); slaveHosts.append(hostPortPair[PORT_NUMBER_INDEX]); } } if (slaveHosts.length() == 0) { throw SQLError.createSQLException( "Must specify at least one slave host to connect to for master/slave replication load-balancing functionality", SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); } masterProps.setProperty(HOST_PROPERTY_KEY, masterHost.toString()); slavesProps.setProperty(HOST_PROPERTY_KEY, slaveHosts.toString()); } return new ReplicationConnection(masterProps, slavesProps); }
/** * Proxies method invocation on the java.sql.Connection interface, trapping "close", "isClosed" * and "commit/rollback" to switch connections for load balancing. This is the continuation of * MultiHostConnectionProxy#invoke(Object, Method, Object[]). */ @Override public synchronized Object invokeMore(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (this.isClosed && !allowedOnClosedConnection(method) && method.getExceptionTypes().length > 0) { if (this.autoReconnect && !this.closedExplicitly) { // try to reconnect first! this.currentConnection = null; pickNewConnection(); this.isClosed = false; this.closedReason = null; } else { 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 (!this.inTransaction) { this.inTransaction = true; this.transactionStartTime = System.nanoTime(); this.transactionCount++; } Object result = null; try { result = method.invoke(this.thisAsConnection, args); if (result != null) { if (result instanceof com.mysql.jdbc.Statement) { ((com.mysql.jdbc.Statement) result).setPingTarget(this); } result = proxyIfReturnTypeIsJdbcInterface(method.getReturnType(), result); } } catch (InvocationTargetException e) { dealWithInvocationException(e); } finally { if ("commit".equals(methodName) || "rollback".equals(methodName)) { this.inTransaction = false; // Update stats String host = this.connectionsToHostsMap.get(this.currentConnection); // avoid NPE if the connection has already been removed from connectionsToHostsMap in // invalidateCurrenctConnection() if (host != null) { synchronized (this.responseTimes) { Integer hostIndex = (this.hostsToListIndexMap.get(host)); if (hostIndex != null && hostIndex < this.responseTimes.length) { this.responseTimes[hostIndex] = System.nanoTime() - this.transactionStartTime; } } } pickNewConnection(); } } return result; }
/** * 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 The list of the hosts to load balance. * @param props Connection properties from where to get initial settings and to be used in new * connections. * @throws SQLException */ private LoadBalancedConnectionProxy(List<String> hosts, Properties props) throws SQLException { super(); 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( "LoadBalancedConnectionProxy.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()); } // hosts specifications may have been reset with settings from a previous connection group int numHosts = initializeHostsSpecs(hosts, props); this.liveConnections = new HashMap<String, ConnectionImpl>(numHosts); this.hostsToListIndexMap = new HashMap<String, Integer>(numHosts); for (int i = 0; i < numHosts; i++) { this.hostsToListIndexMap.put(this.hostList.get(i), i); } this.connectionsToHostsMap = new HashMap<ConnectionImpl, String>(numHosts); this.responseTimes = new long[numHosts]; String retriesAllDownAsString = this.localProps.getProperty("retriesAllDown", "120"); try { this.retriesAllDown = Integer.parseInt(retriesAllDownAsString); } catch (NumberFormatException nfe) { throw SQLError.createSQLException( Messages.getString( "LoadBalancedConnectionProxy.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( "LoadBalancedConnectionProxy.badValueForLoadBalanceBlacklistTimeout", new Object[] {retriesAllDownAsString}), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); } String strategy = this.localProps.getProperty("loadBalanceStrategy", "random"); 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( "LoadBalancedConnectionProxy.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( "LoadBalancedConnectionProxy.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); String lbExceptionChecker = this.localProps.getProperty( "loadBalanceExceptionChecker", "com.mysql.jdbc.StandardLoadBalanceExceptionChecker"); this.exceptionChecker = (LoadBalanceExceptionChecker) Util.loadExtensions( null, props, lbExceptionChecker, "InvalidLoadBalanceExceptionChecker", null) .get(0); pickNewConnection(); }
/** * Proxies method invocation on the java.sql.Connection interface, trapping "close", "isClosed" * and "commit/rollback" (to switch connections for load balancing). */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if ("close".equals(methodName)) { synchronized (this.liveConnections) { // close all underlying connections Iterator allConnections = this.liveConnections.values().iterator(); while (allConnections.hasNext()) { ((Connection) allConnections.next()).close(); } this.liveConnections.clear(); this.connectionsToHostsMap.clear(); } return null; } if ("isClosed".equals(methodName)) { return Boolean.valueOf(this.isClosed); } if (this.isClosed) { throw SQLError.createSQLException( "No operations allowed after connection closed.", SQLError.SQL_STATE_CONNECTION_NOT_OPEN); } if (!inTransaction) { this.inTransaction = true; this.transactionStartTime = getLocalTimeBestResolution(); } Object result = null; try { result = method.invoke(this.currentConn, 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 ("commit".equals(methodName) || "rollback".equals(methodName)) { this.inTransaction = false; // Update stats int hostIndex = ((Integer) this.hostsToListIndexMap.get(this.connectionsToHostsMap.get(this.currentConn))) .intValue(); synchronized (this.responseTimes) { this.responseTimes[hostIndex] = getLocalTimeBestResolution() - this.transactionStartTime; } pickNewConnection(); } } return result; }