/**
* <pre>
* This program is free software; you can redistribute it and/or modify it under the terms of
* the GNU AFFERO GENERAL PUBLIC LICENSE as published by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* 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 AFFERO GENERAL PUBLIC LICENSE for more details.
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* </pre>
*/
package com.meidusa.amoeba.route;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
import org.apache.commons.collections.map.LRUMap;
import org.apache.log4j.Logger;
import com.meidusa.amoeba.config.BeanObjectEntityConfig;
import com.meidusa.amoeba.config.ConfigurationException;
import com.meidusa.amoeba.context.ContextChangedListener;
import com.meidusa.amoeba.context.ProxyRuntimeContext;
import com.meidusa.amoeba.net.Connection;
import com.meidusa.amoeba.net.poolable.ObjectPool;
import com.meidusa.amoeba.parser.ParseException;
import com.meidusa.amoeba.parser.dbobject.Column;
import com.meidusa.amoeba.parser.dbobject.Table;
import com.meidusa.amoeba.parser.function.Function;
import com.meidusa.amoeba.parser.statement.Statement;
import com.meidusa.amoeba.sqljep.function.Comparative;
import com.meidusa.amoeba.sqljep.function.ComparativeBaseList;
import com.meidusa.amoeba.util.Initialisable;
import com.meidusa.amoeba.util.InitialisationException;
import com.meidusa.amoeba.util.StringUtil;
import com.meidusa.amoeba.util.ThreadLocalMap;
import com.meidusa.amoeba.util.Tuple;
/**
* @author struct
*/
public abstract class AbstractQueryRouter<T extends Connection,V> implements QueryRouter<T,V>, Initialisable ,ContextChangedListener {
public static final String _CURRENT_QUERY_OBJECT_ = "_CURRENT_QUERY_OBJECT_";
protected static Logger logger = Logger.getLogger(AbstractQueryRouter.class);
private Map<String,Pattern> patternMap = new HashMap<String,Pattern>();
/* Ĭ��1000 */
private int LRUMapSize = 1000;
protected LRUMap map;
protected Lock mapLock = new ReentrantLock(false);
private Map<Table, TableRule> tableRuleMap = new HashMap<Table, TableRule>();
private Map<Table, TableRule> regexTableRuleMap = new HashMap<Table, TableRule>();
protected Map<String, Function> functionMap = new HashMap<String, Function>();
protected ObjectPool[] defaultPools;
protected ObjectPool[] readPools;
protected ObjectPool[] writePools;
protected Tuple<Statement,ObjectPool[]> tuple;
private File sqlFunctionFile;
private String defaultPool;
private String readPool;
private String writePool;
private boolean needParse = true;
private TableRuleLoader ruleLoader;
public TableRuleLoader getRuleLoader() {
return ruleLoader;
}
public void setRuleLoader(TableRuleLoader ruleLoader) {
this.ruleLoader = ruleLoader;
}
public AbstractQueryRouter(){
}
public void setReadPool(String readPool) {
this.readPool = readPool;
}
public String getReadPool() {
return readPool;
}
public String getWritePool() {
return writePool;
}
public void setWritePool(String writePool) {
this.writePool = writePool;
}
public ObjectPool[] doRoute(T connection,V queryObject) throws ParseException {
if (queryObject == null) {
return defaultPools;
}
if (needParse) {
return selectPool(connection, queryObject);
} else {
return defaultPools;
}
}
protected abstract Map<Table, Map<Column, Comparative>> evaluateTable(T connection,V queryObject);
/**
* ����Query ��route��Ŀ���ַ ObjectPool���� �������null����������DatabaseConnection �����������õ�����
* @throws ParseException
*/
protected void beforeSelectPool(T connection, V queryObject){
ThreadLocalMap.put(_CURRENT_QUERY_OBJECT_, queryObject);
}
protected List<String> evaluate(StringBuffer loggerBuffer,T connection, V queryObject){
boolean isRead = true;
boolean isPrepared = false;
if (queryObject instanceof Request) {
isRead = ((Request) queryObject).isRead();
isPrepared = ((Request) queryObject).isPrepared();
}
List<String> poolNames = new ArrayList<String>();
Map<Table, Map<Column, Comparative>> tables = evaluateTable(connection,queryObject);
if (tables != null && tables.size() > 0) {
Set<Map.Entry<Table, Map<Column, Comparative>>> entrySet = tables.entrySet();
for (Map.Entry<Table, Map<Column, Comparative>> entry : entrySet) {
boolean regexMatched = false;
Map<Column, Comparative> columnMap = entry.getValue();
TableRule tableRule = this.tableRuleMap.get(entry.getKey());
Table table = entry.getKey();
if(tableRule == null && table.getName() != null){
/**
* foreach regex table rule
*/
for(Map.Entry<Table, TableRule> ruleEntry:this.regexTableRuleMap.entrySet()){
Table ruleTable = ruleEntry.getKey();
boolean tableMatched = false;
boolean schemaMatched = false;
/**
* check table name matched or not.
*/
Pattern pattern = this.getPattern(ruleTable.getName());
java.util.regex.Matcher matcher = pattern.matcher(table.getName());
if(matcher.find()){
tableMatched = true;
}
/**
* check table schema matched or not.
*/
pattern = this.getPattern(ruleTable.getSchema().getName());
matcher = pattern.matcher(table.getSchema().getName());
if(matcher.find()){
schemaMatched = true;
}
if(tableMatched && schemaMatched){
tableRule = ruleEntry.getValue();
regexMatched = true;
break;
}
}
}
// �������table Rule ����Ҫ���Ƿ���Rule
if (tableRule != null) {
// û���е�sql��䣬ʹ��Ĭ�ϵ�tableRule
if (columnMap == null || isPrepared) {
String[] pools = (isRead ? tableRule.readPools : tableRule.writePools);
if (pools == null || pools.length == 0) {
pools = tableRule.defaultPools;
}
for (String poolName : pools) {
if (!poolNames.contains(poolName)) {
poolNames.add(poolName);
}
}
if(!isPrepared){
if (logger.isDebugEnabled()) {
loggerBuffer.append(", no Column rule, using table:" + tableRule.table + " default rules:" + Arrays.toString(tableRule.defaultPools));
}
}
continue;
}
List<String> groupMatched = new ArrayList<String>();
for (Rule rule : tableRule.ruleList) {
if (rule.group != null) {
if (groupMatched.contains(rule.group)) {
continue;
}
}
// ��������ȱ���IJ��������٣��������һ������
if (columnMap.size() < rule.parameterMap.size()) {
continue;
} else {
boolean matched = true;
// �����ѯ����а����˸ù�����Ҫ�IJ�������ù�������
for (Column exclude : rule.excludes) {
Comparable<?> condition = columnMap.get(exclude);
if (condition != null) {
matched = false;
break;
}
}
// �����ƥ�佫������һ������
if (!matched) {
continue;
}
Comparable<?>[] comparables = new Comparable[rule.parameterMap.size()];
// �����еIJ���������dmlstatement�д��ڣ������������������
for (Map.Entry<Column, Integer> parameter : rule.cloumnMap.entrySet()) {
Comparative condition = null;
if(regexMatched){
Column column = new Column();
column.setName(parameter.getKey().getName());
column.setTable(table);
condition = columnMap.get(column);
}else{
condition = columnMap.get(parameter.getKey());
}
if (condition != null) {
// ���������� ����� ���������Ҳ�����array ����������Ըù���
if (rule.ignoreArray && condition instanceof ComparativeBaseList) {
matched = false;
break;
}
comparables[parameter.getValue()] = (Comparative) condition.clone();
} else {
matched = false;
break;
}
}
// �����ƥ�佫������һ������
if (!matched) {
continue;
}
try {
Comparable<?> result = rule.rowJep.getValue(comparables);
Integer i = 0;
if (result instanceof Comparative) {
if (rule.result == RuleResult.INDEX) {
i = (Integer) ((Comparative) result).getValue();
if (i < 0) {
continue;
}
matched = true;
} else if(rule.result == RuleResult.POOLNAME){
String matchedPoolsString = ((Comparative) result).getValue().toString();
String[] poolNamesMatched = matchedPoolsString.split(",");
if(poolNamesMatched != null && poolNamesMatched.length >0){
for(String poolName : poolNamesMatched){
if (!poolNames.contains(poolName)) {
poolNames.add(poolName);
}
}
if (logger.isDebugEnabled()) {
loggerBuffer.append(", matched table:" + tableRule.table + ", rule:" + rule.name);
}
}
continue;
}else{
matched = (Boolean) ((Comparative) result).getValue();
}
} else {
if (rule.result == RuleResult.INDEX) {
i = (Integer) Integer.valueOf(result.toString());
if (i < 0) {
continue;
}
matched = true;
} else if(rule.result == RuleResult.POOLNAME){
String matchedPoolsString = result.toString();
String[] poolNamesMatched = StringUtil.split(matchedPoolsString,";,");
if(poolNamesMatched != null && poolNamesMatched.length >0){
for(String poolName : poolNamesMatched){
if (!poolNames.contains(poolName)) {
poolNames.add(poolName);
}
}
if (logger.isDebugEnabled()) {
loggerBuffer.append(", matched table:" + tableRule.table + ", rule:" + rule.name);
}
}
continue;
}else{
matched = (Boolean) result;
}
}
if (matched) {
if (rule.group != null) {
groupMatched.add(rule.group);
}
String[] pools = (isRead ? rule.readPools : rule.writePools);
if (pools == null || pools.length == 0) {
pools = rule.defaultPools;
}
if (pools != null && pools.length > 0) {
if (rule.isSwitch) {
if (!poolNames.contains(pools[i])) {
poolNames.add(pools[i]);
}
} else {
for (String poolName : pools) {
if (!poolNames.contains(poolName)) {
poolNames.add(poolName);
}
}
}
} else {
logger.error("rule:" + rule.name + " matched, but pools is null");
}
if (logger.isDebugEnabled()) {
loggerBuffer.append(", matched table:" + tableRule.table + ", rule:" + rule.name);
}
}
} catch (com.meidusa.amoeba.sqljep.ParseException e) {
// logger.error("parse rule error:"+rule.expression,e);
}
}
}
// ������й�����ƥ�䣬��Ĭ�ϲ���TableRule�е�pool���á�
if (poolNames.size() == 0) {
String[] pools = (isRead ? tableRule.readPools : tableRule.writePools);
if (pools == null || pools.length == 0) {
pools = tableRule.defaultPools;
}
if(!isPrepared){
if(tableRule.ruleList != null && tableRule.ruleList.size()>0){
if (logger.isDebugEnabled()) {
loggerBuffer.append(", no rule matched, using tableRule:[" + tableRule.table + "] defaultPools");
}
}else{
if(logger.isDebugEnabled()){
if(pools != null){
StringBuffer buffer = new StringBuffer();
for(String pool : pools){
buffer.append(pool).append(",");
}
loggerBuffer.append(", using tableRule:[" + tableRule.table + "] defaultPools="+buffer.toString());
}
}
}
}
for (String poolName : pools) {
if (!poolNames.contains(poolName)) {
poolNames.add(poolName);
}
}
}
}
}
}
return poolNames;
}
public ObjectPool[] selectPool(T connection, V queryObject){
beforeSelectPool(connection,queryObject);
StringBuffer loggerBuffer = null;
boolean isRead = true;
if (queryObject instanceof Request) {
isRead = ((Request) queryObject).isRead();
}
if (logger.isDebugEnabled()) {
loggerBuffer = new StringBuffer("query=");
loggerBuffer.append(queryObject);
if(queryObject instanceof Request){
loggerBuffer.append(((Request)queryObject).isPrepared()?",prepared=true":"");
}
}
List<String> poolNames = new ArrayList<String>();
poolNames = evaluate(loggerBuffer,connection,queryObject);
ObjectPool[] pools = new ObjectPool[poolNames.size()];
int i = 0;
for (String name : poolNames) {
ObjectPool pool = ProxyRuntimeContext.getInstance().getPoolMap().get(name);
if(pool == null){
logger.error("cannot found Pool="+name+",sqlObject="+queryObject);
throw new RuntimeException("cannot found Pool="+name+",sqlObject="+queryObject);
}
pools[i++] = pool;
}
if (pools == null || pools.length == 0) {
pools = (isRead ? this.readPools : this.writePools);
if (logger.isDebugEnabled() && pools != null && pools.length > 0) {
if (isRead) {
loggerBuffer.append(", route to queryRouter readPool:" + readPool + "\r\n");
} else {
loggerBuffer.append(", route to queryRouter writePool:" + writePool + "\r\n");
}
}
if (pools == null || pools.length == 0) {
pools = this.defaultPools;
if (logger.isDebugEnabled() && pools != null && pools.length > 0) {
loggerBuffer.append(", route to queryRouter defaultPool:" + defaultPool + "\r\n");
}
}
} else {
if (logger.isDebugEnabled() && pools != null && pools.length > 0) {
loggerBuffer.append(", route to pools:" + poolNames + "\r\n");
}
}
if(logger.isDebugEnabled()){
if(loggerBuffer != null){
logger.debug(loggerBuffer.toString());
}
}
return pools;
}
public void doChange(){
defaultPools = new ObjectPool[] { ProxyRuntimeContext.getInstance().getPoolMap().get(defaultPool) };
if (readPool != null && !StringUtil.isEmpty(readPool)) {
readPools = new ObjectPool[] { ProxyRuntimeContext.getInstance().getPoolMap().get(readPool) };
}
if (writePool != null && !StringUtil.isEmpty(writePool)) {
writePools = new ObjectPool[] { ProxyRuntimeContext.getInstance().getPoolMap().get(writePool) };
}
}
public void init() throws InitialisationException {
defaultPools = new ObjectPool[] { ProxyRuntimeContext.getInstance().getPoolMap().get(defaultPool) };
if (defaultPools == null || defaultPools[0] == null) {
throw new InitialisationException("default pool required!,defaultPool="+defaultPool +" invalid");
}
if (readPool != null && !StringUtil.isEmpty(readPool)) {
ObjectPool pool = ProxyRuntimeContext.getInstance().getPoolMap().get(readPool);
if(pool == null){
logger.error("cannot found Pool="+readPool);
throw new InitialisationException("cannot found Pool="+readPool);
}
readPools = new ObjectPool[] { pool };
}
if (writePool != null && !StringUtil.isEmpty(writePool)) {
ObjectPool pool = ProxyRuntimeContext.getInstance().getPoolMap().get(writePool);
if(pool == null){
logger.error("cannot found Pool="+writePool);
throw new InitialisationException("cannot found Pool="+writePool);
}
writePools = new ObjectPool[] { pool };
}
map = new LRUMap(LRUMapSize);
class ConfigCheckTread extends Thread {
long lastFunFileModified;
private ConfigCheckTread(){
this.setDaemon(true);
this.setName("ruleConfigCheckThread");
if(sqlFunctionFile != null){
lastFunFileModified = sqlFunctionFile.lastModified();
}
}
public void run() {
while (true) {
try {
Thread.sleep(5000l);
Map<String, Function> funMap = null;
Map<Table, TableRule> tableRuleMap = null;
try {
if (AbstractQueryRouter.this.sqlFunctionFile != null) {
if (sqlFunctionFile.lastModified() != lastFunFileModified) {
try {
funMap = loadFunctionMap(AbstractQueryRouter.this.sqlFunctionFile.getAbsolutePath());
logger.info("loading FunctionMap from File="+sqlFunctionFile);
} catch (ConfigurationException exception) {
}
}
}
if(ruleLoader.needLoad()){
tableRuleMap = ruleLoader.loadRule();
if(tableRuleMap != null){
for(Map.Entry<Table, TableRule> ruleEntry:tableRuleMap.entrySet()){
Table ruleTable = ruleEntry.getKey();
if(ruleTable.getName().indexOf("*")>=0
|| (ruleTable.getSchema().getName() != null && ruleTable.getSchema().getName().indexOf("*")>=0)
|| ruleTable.getName().indexOf("^")>=0
|| (ruleTable.getSchema().getName() != null && ruleTable.getSchema().getName().indexOf("^")>=0)){
getPattern(ruleTable.getName());
regexTableRuleMap.put(ruleTable, ruleEntry.getValue());
}
}
}
}
if (funMap != null) {
AbstractQueryRouter.this.functionMap = funMap;
}
if (tableRuleMap != null) {
AbstractQueryRouter.this.tableRuleMap = tableRuleMap;
}
} catch (ConfigurationException e) {
} finally {
if (sqlFunctionFile != null && sqlFunctionFile.exists()) {
lastFunFileModified = sqlFunctionFile.lastModified();
}
}
} catch (InterruptedException e) {
}
}
}
}
if (needParse) {
if (AbstractQueryRouter.this.sqlFunctionFile != null) {
this.functionMap = loadFunctionMap(AbstractQueryRouter.this.sqlFunctionFile.getAbsolutePath());
}
this.tableRuleMap = ruleLoader.loadRule();
if(tableRuleMap != null){
for(Map.Entry<Table, TableRule> ruleEntry:this.tableRuleMap.entrySet()){
Table ruleTable = ruleEntry.getKey();
if(ruleTable.getName().indexOf("*")>=0
|| (ruleTable.getSchema().getName() != null && ruleTable.getSchema().getName().indexOf("*")>=0)
|| ruleTable.getName().indexOf("^")>=0
|| (ruleTable.getSchema().getName() != null && ruleTable.getSchema().getName().indexOf("^")>=0)){
this.getPattern(ruleTable.getName());
regexTableRuleMap.put(ruleTable, ruleEntry.getValue());
}
}
}
new ConfigCheckTread().start();
}
}
public static Map<String, Function> loadFunctionMap(String configFileName) {
FunctionLoader<String, Function> loader = new FunctionLoader<String, Function>() {
@Override
public void initBeanObject(BeanObjectEntityConfig config, Function bean) {
bean.setName(config.getName());
}
@Override
public void putToMap(Map<String, Function> map, Function value) {
map.put(value.getName(), value);
}
};
loader.setDTD("/com/meidusa/amoeba/xml/function.dtd");
loader.setDTDSystemID("function.dtd");
logger.info("loading FunctionMap from File="+configFileName);
return loader.loadFunctionMap(configFileName);
}
public int getLRUMapSize() {
return LRUMapSize;
}
public String getDefaultPool() {
return defaultPool;
}
public void setDefaultPool(String defaultPoolName) {
this.defaultPool = defaultPoolName;
}
public void setLRUMapSize(int mapSize) {
LRUMapSize = mapSize;
}
public File getSqlFunctionFile() {
return sqlFunctionFile;
}
public void setSqlFunctionFile(File sqlFunctionFile) {
this.sqlFunctionFile = sqlFunctionFile;
}
public boolean isNeedParse() {
return needParse;
}
public void setNeedParse(boolean needParse) {
this.needParse = needParse;
}
public ObjectPool getObjectPool(Object key) {
if (key instanceof String) {
return ProxyRuntimeContext.getInstance().getPoolMap().get(key);
} else {
for (ObjectPool pool : ProxyRuntimeContext.getInstance().getPoolMap().values()) {
if (pool.hashCode() == key.hashCode()) {
return pool;
}
}
}
return null;
}
public ObjectPool[] getDefaultObjectPool(){
return this.defaultPools;
}
private Pattern getPattern(String source){
if(source != null && source.indexOf("*") ==0){
source = "^"+source;
}
Pattern pattern = this.patternMap.get(source);
if(pattern == null){
synchronized (patternMap) {
pattern = this.patternMap.get(source);
if(pattern == null){
pattern = Pattern.compile(source);
}
patternMap.put(source, pattern);
}
}
return pattern;
}
public static void main(String[] aa){
String[] aaa = StringUtil.split("asdfasdf,asdf;aqwer",";,");
for(String aaaaa : aaa){
System.out.println(aaaaa);
}
System.out.println(System.currentTimeMillis());
String source = "^fileSys_[a-zA-Z0-9_]*";
Pattern pattern = null;
if(pattern == null){
pattern = Pattern.compile(source);
}
java.util.regex.Matcher matcher = pattern.matcher("fileSys_abc12d");
System.out.println(matcher.matches());
}
}