/*
* Copyright 1999-2012 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cobar.jdbc;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
/**
* @author xianmao.hexm 2012-4-27
*/
public class UrlProvider {
private static final String URL_COBAR_PREFIX = "jdbc:cobar://";
private static final String URL_MYSQL_PREFIX = "jdbc:mysql://";
private static final int PREFIX_LENGTH = URL_COBAR_PREFIX.length();
private static final int SOCKET_CONNECT_TIMEOUT = 10 * 1000;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Random RANDOM = new Random();
/**
* 只处理非空并且以jdbc:cobar://开头的url
*/
public static final String getUrl(String url, Properties info) throws SQLException {
if (url != null && url.regionMatches(true, 0, URL_COBAR_PREFIX, 0, PREFIX_LENGTH)) {
UrlConnection c = null;
try {
ConnectInfo ci = parseUrl(url, info);
c = new UrlConnection(ci.host, ci.port, ci.user, ci.password, ci.database);
c.connect(SOCKET_CONNECT_TIMEOUT);
return selectUrl(c.getServerList(), url, ci);
} catch (Throwable e) {
throw new SQLException(e);
} finally {
if (c != null) {
c.close();
}
}
} else {
return url;
}
}
/**
* 取得MySQL格式的URL
*/
public static final String getMySQLUrl(String url) {
if (url != null && url.regionMatches(true, 0, URL_COBAR_PREFIX, 0, PREFIX_LENGTH)) {
StringBuilder sb = new StringBuilder();
sb.append(URL_MYSQL_PREFIX).append(url.substring(PREFIX_LENGTH));
url = sb.toString();
}
return url;
}
private static ConnectInfo parseUrl(String url, Properties info) {
ConnectInfo ci = new ConnectInfo();
ci.user = info.getProperty("user");
ci.password = info.getProperty("password");
ci.database = info.getProperty("DBNAME");
// 解析参数
int index = url.indexOf('?');
if (index != -1) {
String paramString = url.substring(index + 1);
url = url.substring(0, index);
ci.paramString = paramString;
String[] params = split(paramString, '&');
for (String param : params) {
int indexOfEquals = 0;
if (param != null && (indexOfEquals = param.indexOf('=')) != -1) {
String key = param.substring(0, indexOfEquals);
if ("user".equals(key)) {
if (indexOfEquals + 1 < param.length()) {
ci.user = param.substring(indexOfEquals + 1);
}
} else if ("password".equals(key)) {
if (indexOfEquals + 1 < param.length()) {
ci.password = param.substring(indexOfEquals + 1);
}
} else if ("DBNAME".equals(key)) {
if (indexOfEquals + 1 < param.length()) {
ci.database = param.substring(indexOfEquals + 1);
}
}
}
}
}
// 解析host/port/database
url = url.substring(PREFIX_LENGTH);
String hostStuff = null;
int index2 = url.indexOf('/');
if (index2 != -1) {
hostStuff = url.substring(0, index2);
if (index2 + 1 < url.length()) {
ci.database = url.substring(index2 + 1);
}
} else {
hostStuff = url;
}
// 如果有多个主机只取第一个
int index3 = hostStuff.indexOf(',');
if (index3 != -1) {
hostStuff = hostStuff.substring(0, index3);
}
int index4 = hostStuff.indexOf(':');
if (index4 != -1) {
ci.host = hostStuff.substring(0, index4).trim();
if (index4 + 1 < hostStuff.length()) {
ci.port = Integer.parseInt(hostStuff.substring(index4 + 1).trim());
}
} else {
ci.host = hostStuff.trim();
ci.port = 8066;
}
return ci;
}
/**
* 结合权重和随机策略
*/
private static String selectUrl(List<CobarNode> list, String originUrl, ConnectInfo info) {
int total = 0;
for (CobarNode node : list) {
total += node.getWeight();
}
// 如果总权重小于等于零则使用原来的url
if (total <= 0) {
return originUrl;
}
int rnd = 1 + RANDOM.nextInt(total);
String host = null;
for (CobarNode node : list) {
if ((rnd -= node.getWeight()) <= 0) {
host = node.getHost();
break;
}
}
if (host == null) {
int i = RANDOM.nextInt(list.size());
host = list.get(i).getHost();
}
// 无法取得新的host,则返回原来的url。
if (host == null) {
return originUrl;
}
// 生成新的URL
StringBuilder url = new StringBuilder();
url.append(URL_MYSQL_PREFIX).append(host).append(':').append(info.port);
if (info.database != null) {
url.append('/').append(info.database);
}
if (info.paramString != null) {
url.append('?').append(info.paramString);
}
return url.toString();
}
private static String[] split(String src, char separatorChar) {
if (src == null) {
return null;
}
int length = src.length();
if (length == 0) {
return EMPTY_STRING_ARRAY;
}
List<String> list = new LinkedList<String>();
int i = 0;
int start = 0;
boolean match = false;
while (i < length) {
if (src.charAt(i) == separatorChar) {
if (match) {
list.add(src.substring(start, i));
match = false;
}
start = ++i;
continue;
}
match = true;
i++;
}
if (match) {
list.add(src.substring(start, i));
}
return list.toArray(new String[list.size()]);
}
private static class ConnectInfo {
private String host;
private int port;
private String user;
private String password;
private String database;
private String paramString;
}
}