package com.meidusa.amoeba.config.loader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.log4j.Logger; import org.apache.log4j.helpers.LogLog; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.meidusa.amoeba.config.loader.util.ConfigLoaderUtil; import com.meidusa.amoeba.context.ProxyRuntimeContext; import com.meidusa.amoeba.exception.ConfigurationException; import com.meidusa.amoeba.exception.InitialisationException; import com.meidusa.amoeba.parser.dbobject.Column; import com.meidusa.amoeba.parser.dbobject.Schema; import com.meidusa.amoeba.parser.dbobject.Table; import com.meidusa.amoeba.route.AbstractQueryRouter; import com.meidusa.amoeba.route.Request; import com.meidusa.amoeba.route.Rule; import com.meidusa.amoeba.route.RuleResult; import com.meidusa.amoeba.route.TableRule; import com.meidusa.amoeba.sqljep.RowJEP; import com.meidusa.amoeba.sqljep.function.Abs; import com.meidusa.amoeba.sqljep.function.AddDate; import com.meidusa.amoeba.sqljep.function.AddMonths; import com.meidusa.amoeba.sqljep.function.AddTime; import com.meidusa.amoeba.sqljep.function.Ceil; import com.meidusa.amoeba.sqljep.function.Concat; import com.meidusa.amoeba.sqljep.function.Datediff; import com.meidusa.amoeba.sqljep.function.Day; import com.meidusa.amoeba.sqljep.function.DayName; import com.meidusa.amoeba.sqljep.function.DayOfWeek; import com.meidusa.amoeba.sqljep.function.DayOfYear; import com.meidusa.amoeba.sqljep.function.Decode; import com.meidusa.amoeba.sqljep.function.Floor; import com.meidusa.amoeba.sqljep.function.Hash; import com.meidusa.amoeba.sqljep.function.Hour; import com.meidusa.amoeba.sqljep.function.IndistinctMatching; import com.meidusa.amoeba.sqljep.function.Initcap; import com.meidusa.amoeba.sqljep.function.Instr; import com.meidusa.amoeba.sqljep.function.LastDay; import com.meidusa.amoeba.sqljep.function.Length; import com.meidusa.amoeba.sqljep.function.Lower; import com.meidusa.amoeba.sqljep.function.Lpad; import com.meidusa.amoeba.sqljep.function.Ltrim; import com.meidusa.amoeba.sqljep.function.MakeDate; import com.meidusa.amoeba.sqljep.function.MakeTime; import com.meidusa.amoeba.sqljep.function.Microsecond; import com.meidusa.amoeba.sqljep.function.Minute; import com.meidusa.amoeba.sqljep.function.Modulus; import com.meidusa.amoeba.sqljep.function.Month; import com.meidusa.amoeba.sqljep.function.MonthName; import com.meidusa.amoeba.sqljep.function.MonthsBetween; import com.meidusa.amoeba.sqljep.function.NextDay; import com.meidusa.amoeba.sqljep.function.Nvl; import com.meidusa.amoeba.sqljep.function.PostfixCommand; import com.meidusa.amoeba.sqljep.function.Power; import com.meidusa.amoeba.sqljep.function.Range; import com.meidusa.amoeba.sqljep.function.Replace; import com.meidusa.amoeba.sqljep.function.Round; import com.meidusa.amoeba.sqljep.function.Rpad; import com.meidusa.amoeba.sqljep.function.Rtrim; import com.meidusa.amoeba.sqljep.function.Second; import com.meidusa.amoeba.sqljep.function.Sign; import com.meidusa.amoeba.sqljep.function.SubDate; import com.meidusa.amoeba.sqljep.function.SubTime; import com.meidusa.amoeba.sqljep.function.Substring; import com.meidusa.amoeba.sqljep.function.ToChar; import com.meidusa.amoeba.sqljep.function.ToDate; import com.meidusa.amoeba.sqljep.function.ToLong; import com.meidusa.amoeba.sqljep.function.ToNumber; import com.meidusa.amoeba.sqljep.function.Translate; import com.meidusa.amoeba.sqljep.function.Trim; import com.meidusa.amoeba.sqljep.function.Trunc; import com.meidusa.amoeba.sqljep.function.Upper; import com.meidusa.amoeba.sqljep.function.WeekOfYear; import com.meidusa.amoeba.sqljep.function.Year; import com.meidusa.amoeba.sqljep.variable.Variable; import com.meidusa.amoeba.util.Initialisable; import com.meidusa.amoeba.util.StringUtil; import com.meidusa.amoeba.util.ThreadLocalMap; public class TableRuleFileLoader implements TableRuleLoader, Initialisable, ConfigModifiedAwareLoader { protected static Logger logger = Logger.getLogger(TableRuleFileLoader.class); private Map<String, PostfixCommand> ruleFunctionMap = new HashMap<String, PostfixCommand>(); public final static Map<String, PostfixCommand> ruleFunTab = new HashMap<String, PostfixCommand>(); static { ruleFunTab.put("abs", new Abs()); ruleFunTab.put("power", new Power()); ruleFunTab.put("mod", new Modulus()); ruleFunTab.put("substr", new Substring()); ruleFunTab.put("sign", new Sign()); ruleFunTab.put("ceil", new Ceil()); ruleFunTab.put("floor", new Floor()); ruleFunTab.put("trunc", new Trunc()); ruleFunTab.put("round", new Round()); ruleFunTab.put("length", new Length()); ruleFunTab.put("concat", new Concat()); ruleFunTab.put("instr", new Instr()); ruleFunTab.put("trim", new Trim()); ruleFunTab.put("rtrim", new Rtrim()); ruleFunTab.put("ltrim", new Ltrim()); ruleFunTab.put("rpad", new Rpad()); ruleFunTab.put("lpad", new Lpad()); ruleFunTab.put("lower", new Lower()); ruleFunTab.put("upper", new Upper()); ruleFunTab.put("translate", new Translate()); ruleFunTab.put("replace", new Replace()); ruleFunTab.put("initcap", new Initcap()); ruleFunTab.put("value", new Nvl()); ruleFunTab.put("decode", new Decode()); ruleFunTab.put("to_char", new ToChar()); ruleFunTab.put("to_number", new ToNumber()); ruleFunTab.put("long", new ToLong()); ruleFunTab.put("to_long", new ToLong()); ruleFunTab.put("imatch", new IndistinctMatching()); // replacement for of Oracle's SOUNDEX ruleFunTab.put("months_between", new MonthsBetween()); ruleFunTab.put("add_months", new AddMonths()); ruleFunTab.put("last_day", new LastDay()); ruleFunTab.put("next_day", new NextDay()); ruleFunTab.put("to_date", new ToDate()); // ruleFunTab.put("case", new Case()); // replacement for CASE WHEN digit = 0 THEN ...;WHEN // digit = 1 // THEN...;ELSE... END CASE ruleFunTab.put("index", new Instr()); // maxdb ruleFunTab.put("num", new ToNumber()); // maxdb ruleFunTab.put("chr", new ToChar()); // maxdb ruleFunTab.put("dayname", new DayName()); // maxdb ruleFunTab.put("adddate", new AddDate()); // maxdb ruleFunTab.put("subdate", new SubDate()); // maxdb ruleFunTab.put("addtime", new AddTime()); // maxdb ruleFunTab.put("subtime", new SubTime()); // maxdb ruleFunTab.put("year", new Year()); // maxdb ruleFunTab.put("month", new Month()); // maxdb ruleFunTab.put("day", new Day()); // maxdb ruleFunTab.put("dayofmonth", new Day()); // maxdb ruleFunTab.put("hour", new Hour()); // maxdb ruleFunTab.put("minute", new Minute()); // maxdb ruleFunTab.put("second", new Second()); // maxdb ruleFunTab.put("microsecond", new Microsecond()); // maxdb ruleFunTab.put("datediff", new Datediff()); // maxdb ruleFunTab.put("dayofweek", new DayOfWeek()); // maxdb ruleFunTab.put("weekofyear", new WeekOfYear()); // maxdb ruleFunTab.put("dayofyear", new DayOfYear()); // maxdb ruleFunTab.put("dayname", new DayName()); // maxdb ruleFunTab.put("monthname", new MonthName()); // maxdb ruleFunTab.put("makedate", new MakeDate()); // maxdb ruleFunTab.put("maketime", new MakeTime()); // maxdb ruleFunTab.put("hash", new Hash()); // ruleFunTab.put("range", new Range()); // } Map<String, Variable> variableMap = new HashMap<String, Variable>(); { variableMap.put("isReadStatement", new Variable() { @Override public Comparable<?> getValue() { Object st = (Object) ThreadLocalMap.get(AbstractQueryRouter._CURRENT_QUERY_OBJECT_); if (st instanceof Request) { return ((Request) st).isRead(); } else { return null; } } }); } private String ruleFile; private long lastRuleFileModified; private RuleFunctionMapLoader ruleFuncMapLoader; private ConfigModifiedEventHandler configModifiedEventHandler; public String getRuleFile() { return ruleFile; } public void setRuleFile(String configFile) { try { this.ruleFile = new File(configFile).getCanonicalPath(); } catch (IOException e) { throw new ConfigurationException(e); } } @Override public void init() throws InitialisationException { if (StringUtil.isEmpty(ruleFile) || !new File(ruleFile).exists()) { throw new InitialisationException("rule File not found with name=" + ruleFile); } } public void setRuleFuncMapLoader(RuleFunctionMapLoader ruleFuncMapLoader) { this.ruleFuncMapLoader = ruleFuncMapLoader; } public TableRuleFileLoader() { ruleFunctionMap.putAll(ruleFunTab); } @Override public synchronized Map<Table, TableRule> loadRule() { ruleFuncMapLoader.loadFunctionMap(this.ruleFunctionMap); DocumentBuilder db; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setValidating(true); dbf.setNamespaceAware(false); db = dbf.newDocumentBuilder(); db.setEntityResolver(new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) { if (systemId.endsWith("rule.dtd")) { InputStream in = this.getClass().getResourceAsStream("/com/meidusa/amoeba/xml/rule.dtd"); if (in == null) { LogLog.error("Could not find [rule.dtd]. Used [" + AbstractQueryRouter.class.getClassLoader() + "] class loader in the search."); return null; } else { return new InputSource(in); } } else { return null; } } }); db.setErrorHandler(new ErrorHandler() { public void warning(SAXParseException exception) {} public void error(SAXParseException exception) throws SAXException { logger.error(exception.getMessage() + " at (" + exception.getLineNumber() + ":" + exception.getColumnNumber() + ")"); throw exception; } public void fatalError(SAXParseException exception) throws SAXException { logger.fatal(exception.getMessage() + " at (" + exception.getLineNumber() + ":" + exception.getColumnNumber() + ")"); throw exception; } }); return loadConfigurationFile(ruleFile, db); } catch (Exception e) { logger.fatal("Could not load configuration file, failing", e); throw new ConfigurationException("Error loading configuration file " + ruleFile, e); } finally { lastRuleFileModified = new File(ruleFile).lastModified(); } } private Map<Table, TableRule> loadConfigurationFile(String configFile, DocumentBuilder db) throws InitialisationException { Document doc = null; InputStream is = null; Map<Table, TableRule> tableRuleMap = new HashMap<Table, TableRule>(); try { is = new FileInputStream(new File(configFile)); doc = db.parse(is); } catch (Exception e) { final String s = "Caught exception while loading file " + configFile; logger.error(s, e); throw new ConfigurationException(s, e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { logger.error("Unable to close input stream", e); } } } Element rootElement = doc.getDocumentElement(); NodeList children = rootElement.getChildNodes(); int childSize = children.getLength(); for (int i = 0; i < childSize; i++) { Node childNode = children.item(i); if (childNode instanceof Element) { Element child = (Element) childNode; final String nodeName = child.getNodeName(); if (nodeName.equals("tableRule")) { List<TableRule> list = loadTableRule(child); for (TableRule rule : list) { tableRuleMap.put(rule.table.getName() == null ? null : rule.table, rule); } } } } if (logger.isInfoEnabled()) { logger.info("Loaded rule configuration from: " + configFile); } return tableRuleMap; } private List<TableRule> loadTableRule(Element current) throws InitialisationException { String name = current.getAttribute("name"); String schemaName = current.getAttribute("schema"); List<TableRule> list = new ArrayList<TableRule>(); String[] names = new String[] {name}; if (name != null) { names = name.split(","); } String[] snames = new String[] {schemaName}; if (!StringUtil.isEmpty(schemaName)) { snames = schemaName.split(","); } String usersAttr = current.getAttribute("users"); String[] users = new String[] {usersAttr}; if (usersAttr != null) { users = usersAttr.split(","); } String defaultPools = current.getAttribute("defaultPools"); String[] arrayDefaultPools = null; if (defaultPools != null) { arrayDefaultPools = readTokenizedString(defaultPools, " ,"); } String readPools = current.getAttribute("readPools"); String[] arrayReadPools = null; if (readPools != null) { arrayReadPools = readTokenizedString(readPools, " ,"); } String writePools = current.getAttribute("writePools"); String[] arrayWritePools = null; if (writePools != null) { arrayWritePools = readTokenizedString(writePools, " ,"); } for (String user : users) { if (ProxyRuntimeContext.getInstance().isUserExisted(user)) { for (String sname : snames) { for (String tableName : names) { TableRule tableRule = new TableRule(); Table table = new Table(); table.setUserName(user); String[] tableSchema = StringUtil.split(tableName, "."); if (tableSchema.length >= 2) { String tbName = tableName.substring(tableSchema[0].length() + 1); if ("*".equals(tbName)) { tbName = "^*"; } table.setName(tbName); Schema schema = new Schema(); String sName = tableSchema[0]; if ("*".equals(sName)) { sName = "^*"; } schema.setName(sName); table.setSchema(schema); } else { table.setName(tableName); if (!StringUtil.isEmpty(sname)) { Schema schema = new Schema(); schema.setName(sname); table.setSchema(schema); } } tableRule.defaultPools = arrayDefaultPools; tableRule.readPools = arrayReadPools; tableRule.writePools = arrayWritePools; tableRule.table = table; NodeList children = current.getChildNodes(); int childSize = children.getLength(); for (int i = 0; i < childSize; i++) { Node childNode = children.item(i); if (childNode instanceof Element) { Element child = (Element) childNode; final String nodeName = child.getNodeName(); if (nodeName.equals("rule")) { tableRule.ruleList.add(loadRule(child, tableRule.table)); } } } list.add(tableRule); } } } else { throw new ConfigurationException("user " + user + " not existed"); } } return list; } private Rule loadRule(Element current, Table table) throws InitialisationException { Rule rule = new Rule(); // root rule.name = current.getAttribute("name"); String group = current.getAttribute("group"); rule.group = StringUtil.isEmpty(group) ? null : group; String ignoreArray = current.getAttribute("ignoreArray"); rule.ignoreArray = Boolean.parseBoolean(ignoreArray); String isSwitch = current.getAttribute("isSwitch"); rule.isSwitch = Boolean.parseBoolean(isSwitch); String result = current.getAttribute("ruleResult"); if (!StringUtil.isEmpty(result)) { result = result.toUpperCase(); rule.result = Enum.valueOf(RuleResult.class, result); } // parameters Element parametersNode = ConfigLoaderUtil.getTheOnlyElement(current, "parameters"); if (parametersNode != null) { String[] tokens = readTokenizedString(parametersNode.getTextContent(), " ,"); int index = 0; for (String parameter : tokens) { rule.parameterMap.put(parameter, index); Column column = new Column(); column.setName(parameter); column.setTable(table); rule.cloumnMap.put(column, index); index++; } tokens = readTokenizedString(parametersNode.getAttribute("excludes"), " ,"); if (tokens != null) { for (String parameter : tokens) { Column column = new Column(); column.setName(parameter); column.setTable(table); rule.excludes.add(column); } } } // expression Element expression = ConfigLoaderUtil.getTheOnlyElement(current, "expression"); rule.expression = expression.getTextContent(); rule.rowJep = new RowJEP(rule.expression); try { rule.rowJep.parseExpression(rule.parameterMap, variableMap, this.ruleFunctionMap); } catch (com.meidusa.amoeba.sqljep.ParseException e) { throw new InitialisationException("parser expression:" + rule.expression + " error", e); } // defaultPools Element defaultPoolsNode = ConfigLoaderUtil.getTheOnlyElement(current, "defaultPools"); if (defaultPoolsNode != null) { String defaultPools = defaultPoolsNode.getTextContent(); rule.defaultPools = readTokenizedString(defaultPools, " ,"); } // readPools Element readPoolsNode = ConfigLoaderUtil.getTheOnlyElement(current, "readPools"); if (readPoolsNode != null) { rule.readPools = readTokenizedString(readPoolsNode.getTextContent(), " ,"); } // writePools Element writePoolsNode = ConfigLoaderUtil.getTheOnlyElement(current, "writePools"); if (writePoolsNode != null) { rule.writePools = readTokenizedString(writePoolsNode.getTextContent(), " ,"); } return rule; } public static String[] readTokenizedString(String string, String delim) { return StringUtil.split(string, delim); } @Override public boolean needLoad() { if (new File(ruleFile).lastModified() != lastRuleFileModified) { return true; } if (ruleFuncMapLoader.needLoad()) { return true; } return false; } @Override public Map<Table, TableRule> loadRule(List<Long> ids) { throw new UnsupportedOperationException("Load Rule Config by ids is not supported in class " + this.getClass().getName()); } class ConfigCheckTread extends Thread { private ConfigCheckTread() { this.setDaemon(true); this.setName("ruleConfigCheckThread"); } public void run() { while (true) { try { Thread.sleep(5000l); if (needLoad()) { configModifiedEventHandler.doOnConfigModified(); } } catch (InterruptedException e) {} } } } @Override public Map<Table, TableRule> reLoadRule() { return loadRule(); } @Override public void startObserve() { new ConfigCheckTread().start(); } @Override public void setConfigModifiedEventHandler(ConfigModifiedEventHandler handler) { this.configModifiedEventHandler = handler; } }