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-14)
   */
  package com.alibaba.cobar.config.loader.xml;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  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.loader.SchemaLoader;
  import com.alibaba.cobar.config.model.DataNodeConfig;
  import com.alibaba.cobar.config.model.DataSourceConfig;
  import com.alibaba.cobar.config.model.SchemaConfig;
  import com.alibaba.cobar.config.model.TableConfig;
  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 XMLSchemaLoader implements SchemaLoader {
      private final static String DEFAULT_DTD = "/schema.dtd";
      private final static String DEFAULT_XML = "/schema.xml";
  
      private final Map<String, TableRuleConfig> tableRules;
      private final Set<RuleConfig> rules;
      private final Map<String, RuleAlgorithm> functions;
      private final Map<String, DataSourceConfig> dataSources;
      private final Map<String, DataNodeConfig> dataNodes;
      private final Map<String, SchemaConfig> schemas;
  
      public XMLSchemaLoader(String schemaFile, String ruleFile) {
          XMLRuleLoader ruleLoader = new XMLRuleLoader(ruleFile);
          this.rules = ruleLoader.listRuleConfig();
          this.tableRules = ruleLoader.getTableRules();
          this.functions = ruleLoader.getFunctions();
          ruleLoader = null;
          this.dataSources = new HashMap<String, DataSourceConfig>();
          this.dataNodes = new HashMap<String, DataNodeConfig>();
          this.schemas = new HashMap<String, SchemaConfig>();
          this.load(DEFAULT_DTD, schemaFile == null ? DEFAULT_XML : schemaFile);
      }
  
      public XMLSchemaLoader() {
          this(null, null);
      }
  
      @Override
      public Map<String, TableRuleConfig> getTableRules() {
          return tableRules;
      }
  
      @Override
      public Map<String, RuleAlgorithm> getFunctions() {
          return functions;
      }
  
      @Override
      public Map<String, DataSourceConfig> getDataSources() {
          return (Map<String, DataSourceConfig>) (dataSources.isEmpty() ? Collections.emptyMap() : dataSources);
      }
  
      @Override
      public Map<String, DataNodeConfig> getDataNodes() {
          return (Map<String, DataNodeConfig>) (dataNodes.isEmpty() ? Collections.emptyMap() : dataNodes);
      }
  
      @Override
      public Map<String, SchemaConfig> getSchemas() {
          return (Map<String, SchemaConfig>) (schemas.isEmpty() ? Collections.emptyMap() : schemas);
      }
  
      @Override
      public Set<RuleConfig> listRuleConfig() {
          return rules;
      }
  
      private void load(String dtdFile, String xmlFile) {
          InputStream dtd = null;
          InputStream xml = null;
          try {
              dtd = XMLSchemaLoader.class.getResourceAsStream(dtdFile);
              xml = XMLSchemaLoader.class.getResourceAsStream(xmlFile);
              Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
              loadDataSources(root);
              loadDataNodes(root);
              loadSchemas(root);
          } catch (ConfigException e) {
              throw e;
          } catch (Throwable 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 loadSchemas(Element root) {
          NodeList list = root.getElementsByTagName("schema");
          for (int i = 0, n = list.getLength(); i < n; i++) {
              Element schemaElement = (Element) list.item(i);
              String name = schemaElement.getAttribute("name");
              String dataNode = schemaElement.getAttribute("dataNode");
              // 在非空的情况下检查dataNode是否存在
              if (dataNode != null && dataNode.length() != 0) {
                  checkDataNodeExists(dataNode);
              } else {
                  dataNode = "";// 确保非空
              }
              String group = "default";
              if (schemaElement.hasAttribute("group")) {
                  group = schemaElement.getAttribute("group").trim();
              }
              Map<String, TableConfig> tables = loadTables(schemaElement);
              if (schemas.containsKey(name)) {
                  throw new ConfigException("schema " + name + " duplicated!");
              }
              boolean keepSqlSchema = false;
              if (schemaElement.hasAttribute("keepSqlSchema")) {
                  keepSqlSchema = Boolean.parseBoolean(schemaElement.getAttribute("keepSqlSchema").trim());
              }
              schemas.put(name, new SchemaConfig(name, dataNode, group, keepSqlSchema, tables));
          }
      }
  
      private Map<String, TableConfig> loadTables(Element node) {
          Map<String, TableConfig> tables = new HashMap<String, TableConfig>();
          NodeList nodeList = node.getElementsByTagName("table");
          for (int i = 0; i < nodeList.getLength(); i++) {
              Element tableElement = (Element) nodeList.item(i);
              String name = tableElement.getAttribute("name").toUpperCase();
              String dataNode = tableElement.getAttribute("dataNode");
              TableRuleConfig tableRule = null;
              if (tableElement.hasAttribute("rule")) {
                  String ruleName = tableElement.getAttribute("rule");
                  tableRule = tableRules.get(ruleName);
                  if (tableRule == null) {
                      throw new ConfigException("rule " + ruleName + " is not found!");
                  }
              }
              boolean ruleRequired = false;
              if (tableElement.hasAttribute("ruleRequired")) {
                  ruleRequired = Boolean.parseBoolean(tableElement.getAttribute("ruleRequired"));
              }
  
              String[] tableNames = SplitUtil.split(name, ',', true);
              for (String tableName : tableNames) {
                  TableConfig table = new TableConfig(tableName, dataNode, tableRule, ruleRequired);
                  checkDataNodeExists(table.getDataNodes());
                  if (tables.containsKey(table.getName())) {
                      throw new ConfigException("table " + tableName + " duplicated!");
                  }
                  tables.put(table.getName(), table);
              }
          }
          return tables;
      }
  
      private void checkDataNodeExists(String... nodes) {
          if (nodes == null || nodes.length < 1) {
              return;
          }
          for (String node : nodes) {
              if (!dataNodes.containsKey(node)) {
                  throw new ConfigException("dataNode '" + node + "' is not found!");
              }
          }
      }
  
      private void loadDataNodes(Element root) {
          NodeList list = root.getElementsByTagName("dataNode");
          for (int i = 0, n = list.getLength(); i < n; i++) {
              Element element = (Element) list.item(i);
              String dnNamePrefix = element.getAttribute("name");
              List<DataNodeConfig> confList = new ArrayList<DataNodeConfig>();
              try {
                  Element dsElement = findPropertyByName(element, "dataSource");
                  if (dsElement == null) {
                      throw new NullPointerException("dataNode xml Element with name of " + dnNamePrefix
                              + " has no dataSource Element");
                  }
                  NodeList dataSourceList = dsElement.getElementsByTagName("dataSourceRef");
                  String dataSources[][] = new String[dataSourceList.getLength()][];
                  for (int j = 0, m = dataSourceList.getLength(); j < m; ++j) {
                      Element ref = (Element) dataSourceList.item(j);
                      String dsString = ref.getTextContent();
                      dataSources[j] = SplitUtil.split(dsString, ',', '$', '-', '[', ']');
                  }
                  if (dataSources.length <= 0) {
                      throw new ConfigException("no dataSourceRef defined!");
                  }
                  for (String[] dss : dataSources) {
                      if (dss.length != dataSources[0].length) {
                          throw new ConfigException("dataSource number not equals!");
                      }
                  }
                  for (int k = 0, limit = dataSources[0].length; k < limit; ++k) {
                      StringBuilder dsString = new StringBuilder();
                      for (int dsIndex = 0; dsIndex < dataSources.length; ++dsIndex) {
                          if (dsIndex > 0) {
                              dsString.append(',');
                          }
                          dsString.append(dataSources[dsIndex][k]);
                      }
                      DataNodeConfig conf = new DataNodeConfig();
                      ParameterMapping.mapping(conf, ConfigUtil.loadElements(element));
                      confList.add(conf);
                      switch (k) {
                      case 0:
                          conf.setName((limit == 1) ? dnNamePrefix : dnNamePrefix + "[" + k + "]");
                          break;
                      default:
                          conf.setName(dnNamePrefix + "[" + k + "]");
                          break;
                      }
                      conf.setDataSource(dsString.toString());
                  }
              } catch (Exception e) {
                  throw new ConfigException("dataNode " + dnNamePrefix + " define error", e);
              }
  
              for (DataNodeConfig conf : confList) {
                  if (dataNodes.containsKey(conf.getName())) {
                      throw new ConfigException("dataNode " + conf.getName() + " duplicated!");
                  }
                  dataNodes.put(conf.getName(), conf);
              }
          }
      }
  
      private void loadDataSources(Element root) {
          NodeList list = root.getElementsByTagName("dataSource");
          for (int i = 0, n = list.getLength(); i < n; ++i) {
              Element element = (Element) list.item(i);
              ArrayList<DataSourceConfig> dscList = new ArrayList<DataSourceConfig>();
              String dsNamePrefix = element.getAttribute("name");
              try {
                  String dsType = element.getAttribute("type");
                  Element locElement = findPropertyByName(element, "location");
                  if (locElement == null) {
                      throw new NullPointerException("dataSource xml Element with name of " + dsNamePrefix
                              + " has no location Element");
                  }
                  NodeList locationList = locElement.getElementsByTagName("location");
                  int dsIndex = 0;
                  for (int j = 0, m = locationList.getLength(); j < m; ++j) {
                      String locStr = ((Element) locationList.item(j)).getTextContent();
                      int colonIndex = locStr.indexOf(':');
                      int slashIndex = locStr.indexOf('/');
                      String dsHost = locStr.substring(0, colonIndex).trim();
                      int dsPort = Integer.parseInt(locStr.substring(colonIndex + 1, slashIndex).trim());
                      String[] schemas = SplitUtil.split(locStr.substring(slashIndex + 1).trim(), ',', '$', '-');
                      for (String dsSchema : schemas) {
                          DataSourceConfig dsConf = new DataSourceConfig();
                          ParameterMapping.mapping(dsConf, ConfigUtil.loadElements(element));
                          dscList.add(dsConf);
                          switch (dsIndex) {
                          case 0:
                              dsConf.setName(dsNamePrefix);
                              break;
                          case 1:
                              dscList.get(0).setName(dsNamePrefix + "[0]");
                          default:
                              dsConf.setName(dsNamePrefix + "[" + dsIndex + "]");
                          }
                          dsConf.setType(dsType);
                          dsConf.setDatabase(dsSchema);
                          dsConf.setHost(dsHost);
                          dsConf.setPort(dsPort);
                          ++dsIndex;
                      }
                  }
              } catch (Exception e) {
                  throw new ConfigException("dataSource " + dsNamePrefix + " define error", e);
              }
              for (DataSourceConfig dsConf : dscList) {
                  if (dataSources.containsKey(dsConf.getName())) {
                      throw new ConfigException("dataSource name " + dsConf.getName() + "duplicated!");
                  }
                  dataSources.put(dsConf.getName(), dsConf);
              }
          }
      }
  
      private static Element findPropertyByName(Element bean, String name) {
          NodeList propertyList = bean.getElementsByTagName("property");
          for (int j = 0, m = propertyList.getLength(); j < m; ++j) {
              Node node = propertyList.item(j);
              if (node instanceof Element) {
                  Element p = (Element) node;
                  if (name.equals(p.getAttribute("name"))) {
                      return p;
                  }
              }
          }
          return null;
      }
  
  }
  