package com.meidusa.amoeba.route;
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.BeanObjectEntityConfig;
import com.meidusa.amoeba.config.ConfigurationException;
import com.meidusa.amoeba.config.DocumentUtil;
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.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.InitialisationException;
import com.meidusa.amoeba.util.StringUtil;
import com.meidusa.amoeba.util.ThreadLocalMap;
public class TableRuleFileLoader implements TableRuleLoader,Initialisable {
protected static Logger logger = Logger.getLogger(AbstractQueryRouter.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 File ruleFile;
private long lastRuleModified;
private long lastRuleFunctionFileModified;
private File functionFile;
public File getRuleFile() {
return ruleFile;
}
public void setRuleFile(File configFile) {
this.ruleFile = configFile;
}
public File getFunctionFile() {
return functionFile;
}
public void setFunctionFile(File functionConfigFile) {
this.functionFile = functionConfigFile;
}
@Override
public void init() throws InitialisationException {
if (functionFile == null || !functionFile.exists()) {
throw new InitialisationException("rule function File not found with name="+functionFile);
}
}
public TableRuleFileLoader(){
ruleFunctionMap.putAll(ruleFunTab);
}
@Override
public synchronized Map<Table, TableRule> loadRule() {
if (functionFile != null && functionFile.exists()) {
lastRuleFunctionFileModified = functionFile.lastModified();
}
Map<String, PostfixCommand> ruleFunMap = null;
ruleFunMap = loadRuleFunctionMap(functionFile.getAbsolutePath());
if (ruleFunMap != null) {
Map<String, PostfixCommand> temp = new HashMap<String, PostfixCommand>();
temp.putAll(ruleFunTab);
ruleFunTab.putAll(ruleFunMap);
this.ruleFunctionMap = ruleFunMap;
}
logger.info("loading ruleFunMap from File="+functionFile);
lastRuleModified = ruleFile.lastModified();
DocumentBuilder db;
logger.info("loading tableRule from File="+ruleFile.getAbsolutePath());
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 = AbstractQueryRouter.class.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);
}
}
private Map<Table, TableRule> loadConfigurationFile(File fileName, DocumentBuilder db)
throws InitialisationException {
Document doc = null;
InputStream is = null;
Map<Table, TableRule> tableRuleMap = new HashMap<Table, TableRule>();
try {
is = new FileInputStream(fileName);
doc = db.parse(is);
} catch (Exception e) {
final String s = "Caught exception while loading file " + fileName.getAbsolutePath();
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: " + fileName);
}
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 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 tableName : names){
TableRule tableRule = new TableRule();
Table table = new Table();
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(schemaName)) {
Schema schema = new Schema();
schema.setName(schemaName);
table.setSchema(schema);
}
}
tableRule.defaultPools = arrayDefaultPools;
tableRule.readPools = arrayReadPools;
tableRule.writePools = arrayWritePools;
tableRule.table = table;
list.add(tableRule);
}
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")) {
for(TableRule tableRule :list){
tableRule.ruleList.add(loadRule(child, tableRule.table));
}
}
}
}
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 = DocumentUtil.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 = DocumentUtil.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 = DocumentUtil.getTheOnlyElement(current, "defaultPools");
if (defaultPoolsNode != null) {
String defaultPools = defaultPoolsNode.getTextContent();
rule.defaultPools = readTokenizedString(defaultPools, " ,");
}
// readPools
Element readPoolsNode = DocumentUtil.getTheOnlyElement(current, "readPools");
if (readPoolsNode != null) {
rule.readPools = readTokenizedString(readPoolsNode.getTextContent(), " ,");
}
// writePools
Element writePoolsNode = DocumentUtil.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 (ruleFile.lastModified() != lastRuleModified) {
return true;
}
if (functionFile != null && functionFile.exists()
&& functionFile.lastModified() != lastRuleFunctionFileModified ) {
return true;
}
return false;
}
public static Map<String, PostfixCommand> loadRuleFunctionMap(String configFileName) {
FunctionLoader<String, PostfixCommand> loader = new FunctionLoader<String, PostfixCommand>() {
@Override
public void initBeanObject(BeanObjectEntityConfig config, PostfixCommand bean) {
bean.setName(config.getName());
}
@Override
public void putToMap(Map<String, PostfixCommand> map, PostfixCommand value) {
map.put(value.getName(), value);
}
};
loader.setDTD("/com/meidusa/amoeba/xml/function.dtd");
loader.setDTDSystemID("function.dtd");
Map<String, PostfixCommand> tempRuleFunMap = new HashMap<String, PostfixCommand>();
logger.info("loading RuleFunctionMap from File="+configFileName);
Map<String, PostfixCommand> defindMap = loader.loadFunctionMap(configFileName);
tempRuleFunMap.putAll(ruleFunTab);
tempRuleFunMap.putAll(defindMap);
return tempRuleFunMap;
}
}