content: /*
   * 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.
   */
  /**
   * (created at 2012-6-13)
   */
  package com.alibaba.cobar.config.loader.xml;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.sql.SQLSyntaxErrorException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  import com.alibaba.cobar.config.model.rule.RuleAlgorithm;
  import com.alibaba.cobar.config.model.rule.RuleConfig;
  import com.alibaba.cobar.config.model.rule.TableRuleConfig;
  import com.alibaba.cobar.config.util.ConfigException;
  import com.alibaba.cobar.config.util.ConfigUtil;
  import com.alibaba.cobar.config.util.ParameterMapping;
  import com.alibaba.cobar.util.SplitUtil;
  
  /**
   * @author <a href="mailto:shuo.qius@alibaba-inc.com">QIU Shuo</a>
   */
  @SuppressWarnings("unchecked")
  public class XMLRuleLoader {
      private final static String DEFAULT_DTD = "/rule.dtd";
      private final static String DEFAULT_XML = "/rule.xml";
  
      private final Map<String, TableRuleConfig> tableRules;
      private final Set<RuleConfig> rules;
      private final Map<String, RuleAlgorithm> functions;
  
      public XMLRuleLoader(String ruleFile) {
          this.rules = new HashSet<RuleConfig>();
          this.tableRules = new HashMap<String, TableRuleConfig>();
          this.functions = new HashMap<String, RuleAlgorithm>();
          load(DEFAULT_DTD, ruleFile == null ? DEFAULT_XML : ruleFile);
      }
  
      public XMLRuleLoader() {
          this(null);
      }
  
      public Map<String, TableRuleConfig> getTableRules() {
          return (Map<String, TableRuleConfig>) (tableRules.isEmpty() ? Collections.emptyMap() : tableRules);
      }
  
      public Set<RuleConfig> listRuleConfig() {
          return (Set<RuleConfig>) ((rules == null || rules.isEmpty()) ? Collections.emptySet() : rules);
      }
  
      public Map<String, RuleAlgorithm> getFunctions() {
          return (Map<String, RuleAlgorithm>) (functions.isEmpty() ? Collections.emptyMap() : functions);
      }
  
      private void load(String dtdFile, String xmlFile) {
          InputStream dtd = null;
          InputStream xml = null;
          try {
              dtd = XMLRuleLoader.class.getResourceAsStream(dtdFile);
              xml = XMLRuleLoader.class.getResourceAsStream(xmlFile);
              Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
              loadFunctions(root);
              loadTableRules(root);
          } catch (ConfigException e) {
              throw e;
          } catch (Exception e) {
              throw new ConfigException(e);
          } finally {
              if (dtd != null) {
                  try {
                      dtd.close();
                  } catch (IOException e) {
                  }
              }
              if (xml != null) {
                  try {
                      xml.close();
                  } catch (IOException e) {
                  }
              }
          }
      }
  
      private void loadTableRules(Element root) throws SQLSyntaxErrorException {
          NodeList list = root.getElementsByTagName("tableRule");
          for (int i = 0, n = list.getLength(); i < n; ++i) {
              Node node = list.item(i);
              if (node instanceof Element) {
                  Element e = (Element) node;
                  String name = e.getAttribute("name");
                  if (tableRules.containsKey(name)) {
                      throw new ConfigException("table rule " + name + " duplicated!");
                  }
                  NodeList ruleNodes = e.getElementsByTagName("rule");
                  int length = ruleNodes.getLength();
                  List<RuleConfig> ruleList = new ArrayList<RuleConfig>(length);
                  for (int j = 0; j < length; ++j) {
                      RuleConfig rule = loadRule((Element) ruleNodes.item(j));
                      ruleList.add(rule);
                      rules.add(rule);
                  }
                  tableRules.put(name, new TableRuleConfig(name, ruleList));
              }
          }
      }
  
      private RuleConfig loadRule(Element element) throws SQLSyntaxErrorException {
          Element columnsEle = ConfigUtil.loadElement(element, "columns");
          String[] columns = SplitUtil.split(columnsEle.getTextContent(), ',', true);
          for (int i = 0; i < columns.length; ++i) {
              columns[i] = columns[i].toUpperCase();
          }
          Element algorithmEle = ConfigUtil.loadElement(element, "algorithm");
          String algorithm = algorithmEle.getTextContent();
          return new RuleConfig(columns, algorithm);
      }
  
      private void loadFunctions(Element root) throws ClassNotFoundException, InstantiationException,
              IllegalAccessException, InvocationTargetException {
          NodeList list = root.getElementsByTagName("function");
          for (int i = 0, n = list.getLength(); i < n; ++i) {
              Node node = list.item(i);
              if (node instanceof Element) {
                  Element e = (Element) node;
                  String name = e.getAttribute("name");
                  if (functions.containsKey(name)) {
                      throw new ConfigException("rule function " + name + " duplicated!");
                  }
                  String clazz = e.getAttribute("class");
                  RuleAlgorithm function = createFunction(name, clazz);
                  ParameterMapping.mapping(function, ConfigUtil.loadElements(e));
                  functions.put(name, function);
              }
          }
      }
  
      private RuleAlgorithm createFunction(String name, String clazz) throws ClassNotFoundException,
              InstantiationException, IllegalAccessException, InvocationTargetException {
          Class<?> clz = Class.forName(clazz);
          if (!RuleAlgorithm.class.isAssignableFrom(clz)) {
              throw new IllegalArgumentException("rule function must implements " + RuleAlgorithm.class.getName()
                      + ", name=" + name);
          }
          Constructor<?> constructor = null;
          for (Constructor<?> cons : clz.getConstructors()) {
              Class<?>[] paraClzs = cons.getParameterTypes();
              if (paraClzs != null && paraClzs.length == 1) {
                  Class<?> paraClzs1 = paraClzs[0];
                  if (String.class.isAssignableFrom(paraClzs1)) {
                      constructor = cons;
                      break;
                  }
              }
          }
          if (constructor == null) {
              throw new ConfigException("function " + name + " with class of " + clazz
                      + " must have a constructor with one parameter: String funcName");
          }
          return (RuleAlgorithm) constructor.newInstance(name);
      }
  
  }
  