/** * <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.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import org.apache.commons.collections.map.LRUMap; import org.apache.log4j.Logger; import com.meidusa.amoeba.config.loader.ConfigModifiedAwareLoader; import com.meidusa.amoeba.config.loader.ConfigModifiedEventHandler; import com.meidusa.amoeba.config.loader.SqlFunctionMapLoader; import com.meidusa.amoeba.config.loader.TableRuleLoader; import com.meidusa.amoeba.context.ContextChangedListener; import com.meidusa.amoeba.context.ProxyRuntimeContext; import com.meidusa.amoeba.exception.ConfigurationException; import com.meidusa.amoeba.exception.InitialisationException; import com.meidusa.amoeba.net.Connection; import com.meidusa.amoeba.net.poolable.ObjectPool; import com.meidusa.amoeba.parser.AmoebaSqlHintPropNames; 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.ConcurrentHashSet; import com.meidusa.amoeba.util.Initialisable; 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 extends Request> implements QueryRouter<T, V>, Initialisable, ContextChangedListener, ConfigModifiedEventHandler { public static final String _CURRENT_QUERY_OBJECT_ = "_CURRENT_QUERY_OBJECT_"; protected static Logger logger = Logger.getLogger(AbstractQueryRouter.class); private ConcurrentHashMap<String, Pattern> patternMap = new ConcurrentHashMap<String, Pattern>(); /* 默认1000 */ private int LRUMapSize = 1000; private Long routerID; protected Map<String, LRUMap> map; private Map<Table, TableRule> tableRuleMap = new ConcurrentHashMap<Table, TableRule>(); private Map<Table, TableRule> regexTableRuleMap = new ConcurrentHashMap<Table, TableRule>(); private Map<String, Set<String>> userWithTableRule = new ConcurrentHashMap<String, Set<String>>(); protected Map<String, Function> sqlFunctionMap = new ConcurrentHashMap<String, Function>(); protected Tuple<Statement, ObjectPool[]> tuple; private SqlFunctionMapLoader sqlFuncMapLoader; // 以下为pool的名称字符串 private String defaultPool; private String readPool; private String writePool; // 以下为真实的ObjectPool protected ObjectPool[] defaultPools; protected ObjectPool[] readPools; protected ObjectPool[] writePools; private boolean needParse = true; private TableRuleLoader ruleLoader; public void setSqlFuncMapLoader(SqlFunctionMapLoader sqlFuncMapLoader) { this.sqlFuncMapLoader = sqlFuncMapLoader; } public ObjectPool[] doRoute(T connection, V queryObject, Statement statement) throws ParseException { if (queryObject == null) { return defaultPools; } if (needParse) { // route from sql hint if (isSpecifiedPoolsInSqlHint(statement)) { String poolsHint = (String) statement.getHintParams().get(AmoebaSqlHintPropNames.POOLS_HINT); String[] poolNames = poolsHint.split(","); ObjectPool[] pools = new ObjectPool[poolNames.length]; int i = 0; for (String name : poolNames) { name = name.trim(); 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; } return pools; } else { return selectPool(connection, queryObject, statement); } } else { return defaultPools; } } private boolean isSpecifiedPoolsInSqlHint(Statement statement) { boolean isSpecifiedPoolsInSqlHint = false; if (statement != null && statement.getHintParams() != null ) { String pools = (String) statement.getHintParams().get(AmoebaSqlHintPropNames.POOLS_HINT); if (!StringUtil.isEmpty(pools)) { isSpecifiedPoolsInSqlHint = true; } } return isSpecifiedPoolsInSqlHint; } protected abstract Map<Table, Map<Column, Comparative>> evaluateTable(T connection, V queryObject, Statement statement); /** * 返回Query 被route到目标地址 ObjectPool集合 如果返回null,则是属于DatabaseConnection 自身属性设置的请求。 * * @throws ParseException */ protected void beforeSelectPool(T connection, V queryObject, Statement statement) { ThreadLocalMap.put(_CURRENT_QUERY_OBJECT_, queryObject); } protected List<String> evaluate(StringBuffer loggerBuffer, T connection, V queryObject, Statement statement) { boolean isRead = true; boolean isPrepared = false; isRead = queryObject.isRead(); isPrepared = queryObject.isPrepared(); List<String> poolNames = new ArrayList<String>(); Map<Table, Map<Column, Comparative>> tables = evaluateTable(connection, queryObject, statement); 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()); // admin对于单表的规则,是取不到值的,如需支持非*的表规则,这里要重新取值 if(connection.isAdmin() && tableRule == null) { Set<Table> keySet = this.tableRuleMap.keySet(); Table tableFromClient = entry.getKey(); for(Table table : keySet) { if (table.equalsForAdminUser(tableFromClient)) { tableRule = this.tableRuleMap.get(table); break; } } } // sql语句中包含的表 Table table = entry.getKey(); // 强制指定schema if (tableRule == null && !StringUtil.isEmpty(table.getName()) && table.getSchema() != null && !StringUtil.isEmpty(table.getSchema().getName())) { /** * foreach regex table rule */ for (Map.Entry<Table, TableRule> ruleEntry : this.regexTableRuleMap.entrySet()) { // 表规则中包含的表 Table ruleTable = ruleEntry.getKey(); boolean tableMatched = false; boolean schemaMatched = false; boolean userIdMatched = 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. */ if (table.getSchema().equals(ruleTable.getSchema())) { schemaMatched = true; } // check userid matched or not. if (connection.isAdmin()) { userIdMatched = true; } else { if (ruleTable.getUserName().equals(table.getUserName())) { userIdMatched = true; } } if (tableMatched && schemaMatched && userIdMatched) { 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>(); if (tableRule.ruleList != null && tableRule.ruleList.size() > 0) { for (Rule rule : tableRule.ruleList) { if (rule.group != null) { if (groupMatched.contains(rule.group)) { continue; } } boolean matched = true; // 如果查询语句中包含了该规则不需要的参数,则该规则将被忽略 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()]; // 规则中参数如果在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(); } } // 如果不匹配将继续下一条规则 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); } } } // 如果所有规则都无法匹配,则默认采用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, Statement statement) { beforeSelectPool(connection, queryObject, statement); StringBuffer loggerBuffer = null; boolean isRead = true; isRead = queryObject.isRead(); if (logger.isDebugEnabled()) { loggerBuffer = new StringBuffer("query="); loggerBuffer.append(queryObject); loggerBuffer.append(queryObject.isPrepared() ? ",prepared=true" : ""); } List<String> poolNames = new ArrayList<String>(); poolNames = evaluate(loggerBuffer, connection, queryObject, statement); 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) { // 如果不是use语句才有可能被转到default pool,use 语句就不能再有这样的尝试,该是null还是null if (!queryObject.isUseStatement()) { 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 { if (defaultPool == null || defaultPool.isEmpty()) { throw new InitialisationException("default pool required!"); } 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); if (needParse) { sqlFuncMapLoader.loadFunctionMap(this.sqlFunctionMap); // 如果配置了ruleLoader if (ruleLoader != null) { this.tableRuleMap = ruleLoader.loadRule(); if (tableRuleMap != null) { for (Map.Entry<Table, TableRule> ruleEntry : this.tableRuleMap.entrySet()) { Table table = ruleEntry.getKey(); String tableName = table.getName(); String schemaName = table.getSchema().getName(); String userName = table.getUserName(); storeUserSchemaMap(userName, schemaName, this.userWithTableRule); if (tableName.indexOf("*") >= 0 || (schemaName != null && schemaName.indexOf("*") >= 0) || tableName.indexOf("^") >= 0 || (schemaName != null && schemaName.indexOf("^") >= 0)) { this.getPattern(tableName); this.regexTableRuleMap.put(table, ruleEntry.getValue()); } } } } } if (ruleLoader instanceof ConfigModifiedAwareLoader) { ((ConfigModifiedAwareLoader) ruleLoader).setConfigModifiedEventHandler(this); ((ConfigModifiedAwareLoader) ruleLoader).startObserve(); } } 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 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) { Pattern pattern = null; if (!StringUtil.isEmpty(source)) { if (source.indexOf("*") == 0) { source = "^" + source; } pattern = this.patternMap.putIfAbsent(source, Pattern.compile(source)); } return pattern; } 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 Long getRouterID() { return routerID; } public void setRouterID(Long routerID) { this.routerID = routerID; } public boolean isExistedTableRuleForUser(String username) { if (userWithTableRule.containsKey(username)) { Set<String> schemasUserCanAccess = userWithTableRule.get(username); if (schemasUserCanAccess.size() > 0) { return true; } } return false; } public boolean isSchemaExistedForUser(String userName, String schemaName) { if (userWithTableRule.containsKey(userName)) { Set<String> schemasUserCanAccess = userWithTableRule.get(userName); if (schemasUserCanAccess.contains(schemaName)) { return true; } } return false; } private void storeUserSchemaMap(String userName, String schemaName, Map<String, Set<String>> userWithTableRule) { if (!userWithTableRule.containsKey(userName)) { Set<String> schemaNames = new ConcurrentHashSet<String>(); schemaNames.add(schemaName); userWithTableRule.put(userName, schemaNames); } else { Set<String> schemaNames = userWithTableRule.get(userName); schemaNames.add(schemaName); } } @Override public void doOnConfigModified() { try { Map<String, Function> funMap = null; Map<Table, TableRule> tableRuleMap = null; Map<String, Set<String>> userWithTableRule = null; Map<Table, TableRule> regexTableRuleMap = null; if (sqlFuncMapLoader.needLoad()) { funMap = new ConcurrentHashMap<String, Function>(); sqlFuncMapLoader.loadFunctionMap(funMap); } tableRuleMap = ruleLoader.loadRule(); if (tableRuleMap != null) { userWithTableRule = new ConcurrentHashMap<String, Set<String>>(); regexTableRuleMap = new ConcurrentHashMap<Table, TableRule>(); for (Map.Entry<Table, TableRule> ruleEntry : tableRuleMap.entrySet()) { Table table = ruleEntry.getKey(); String tableName = table.getName(); String schemaName = table.getSchema().getName(); String userName = table.getUserName(); storeUserSchemaMap(userName, schemaName, userWithTableRule); if (tableName.indexOf("*") >= 0 || (schemaName != null && schemaName.indexOf("*") >= 0) || tableName.indexOf("^") >= 0 || (schemaName != null && schemaName.indexOf("^") >= 0)) { getPattern(tableName); regexTableRuleMap.put(table, ruleEntry.getValue()); } } } if (funMap != null) { this.sqlFunctionMap = funMap; } if (tableRuleMap != null) { this.tableRuleMap = tableRuleMap; if (regexTableRuleMap != null) { this.regexTableRuleMap = regexTableRuleMap; } if (userWithTableRule != null) { this.userWithTableRule = userWithTableRule; } } } catch (ConfigurationException e) {} } 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()); } }