/*
* Copyright 2014-2015 the original author or authors
*
* 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.
*/
package com.wplatform.ddal.config.parser;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import com.wplatform.ddal.config.Configuration;
import com.wplatform.ddal.config.DataNodeConfig;
import com.wplatform.ddal.config.DataSourceException;
import com.wplatform.ddal.config.RuleAlgorithmConfig;
import com.wplatform.ddal.config.SchemaConfig;
import com.wplatform.ddal.config.ShardConfig;
import com.wplatform.ddal.config.ShardConfig.ShardItem;
import com.wplatform.ddal.config.TableConfig;
import com.wplatform.ddal.config.XmlDataSourceProvider;
import com.wplatform.ddal.dispatch.rule.RuleExpression;
import com.wplatform.ddal.dispatch.rule.TableNode;
import com.wplatform.ddal.dispatch.rule.TableRouter;
import com.wplatform.ddal.util.New;
import com.wplatform.ddal.util.StringUtils;
import com.wplatform.ddal.util.Utils;
public class XmlConfigParser {
private XPathParser parser;
private Configuration configuration;
public XmlConfigParser(InputStream inputStream) {
this(new XPathParser(inputStream, true, new ConfigEntityResolver()));
}
public XmlConfigParser(XPathParser parser) {
this.configuration = new Configuration();
this.parser = parser;
}
public Configuration parse() {
try {
parseElement(parser.evalNode("/ddal-config"));
return configuration;
} catch (ParsingException e) {
throw e;
} catch (Exception e) {
throw new ParsingException("Error parsing ddal-config XML . Cause: " + e, e);
}
}
private void parseElement(XNode xNode) {
parseSettings(xNode.evalNodes("/ddal-config/settings/property"));
parseShards(xNode.evalNodes("/ddal-config/cluster/shard"));
parseDataSource(xNode.evalNodes("/ddal-config/dataNodes/datasource"));
parseRuleConfig(xNode.evalNodes("/ddal-config/tableRules/tableRule"));
parseRuleAlgorithms(xNode.evalNodes("/ddal-config/ruleAlgorithms/ruleAlgorithm"));
parseSchemaConfig(xNode.evalNode("/ddal-config/schema"));
}
private void parseSettings(List<XNode> xNodes) {
for (XNode xNode : xNodes) {
String proName = xNode.getStringAttribute("name");
String proValue = xNode.getStringAttribute("value");
if (StringUtils.isNullOrEmpty(proName)) {
throw new ParsingException(
"Error parsing ddal-config XML . Cause: propery's name required.");
}
if (StringUtils.isNullOrEmpty(proValue)) {
throw new ParsingException(
"Error parsing ddal-config XML . Cause: propery's value required.");
}
configuration.setProperty(proName, proValue);
}
}
private void parseShards(List<XNode> xNodes) {
for (XNode xNode : xNodes) {
ShardConfig shardConfig = new ShardConfig();
String name = xNode.getStringAttribute("name");
if (StringUtils.isNullOrEmpty(name)) {
throw new ParsingException(
"Error parsing ddal-config XML . Cause: element cluster.shard's name required.");
}
shardConfig.setName(name);
List<XNode> children = xNode.evalNodes("member");
List<ShardItem> shardItems = New.arrayList(children.size());
for (XNode child : children) {
ShardItem shardItem = new ShardItem();
String ref = child.getStringAttribute("ref");
int wWeight, rWeight;
try {
wWeight = child.getIntAttribute("wWeight",1);
rWeight = child.getIntAttribute("rWeight",1);
} catch (Exception e) {
throw new ParsingException("incorrect wWeight or rWeight 'value for member");
}
if (StringUtils.isNullOrEmpty(ref)) {
throw new ParsingException("member 's ref is required.");
}
if (wWeight <= 0 && rWeight <= 0) {
throw new ParsingException("member 's weight not be less than zero.");
}
if(wWeight <= 0) {
shardItem.setReadOnly(true);
}
shardItem.setRef(ref);
shardItem.setwWeight(wWeight);
shardItem.setrWeight(rWeight);
if (shardItems.contains(shardItem)) {
throw new ParsingException("Duplicate datasource reference in " + name);
}
shardItems.add(shardItem);
}
shardConfig.setShardItems(shardItems);
configuration.addShard(name, shardConfig);
}
}
private void parseDataSource(List<XNode> xNodes) {
XmlDataSourceProvider provider = new XmlDataSourceProvider();
for (XNode dataSourceNode : xNodes) {
DataNodeConfig dsConfig = new DataNodeConfig();
String id = dataSourceNode.getStringAttribute("id");
String jndiName = dataSourceNode.getStringAttribute("jndi-name");
String clazz = dataSourceNode.getStringAttribute("class");
if (!StringUtils.isNullOrEmpty(id)) {
dsConfig.setId(id);
if (!StringUtils.isNullOrEmpty(jndiName)) {
dsConfig.setJndiName(jndiName);
} else if (!StringUtils.isNullOrEmpty(clazz)) {
dsConfig.setClazz(clazz);
} else {
throw new ParsingException("datasource must be 'jndi-name' 'class' type.");
}
dsConfig.setProperties(dataSourceNode.getChildrenAsProperties());
DataSource dataSource = constructDataSource(dsConfig);
provider.addDataNode(id, dataSource);
} else {
throw new ParsingException(
"Error parsing ddal-config XML . Cause: datasource attribute 'id' required.");
}
}
configuration.setDataSourceProvider(provider);
}
private void parseRuleConfig(List<XNode> xNodes) {
for (XNode xNode : xNodes) {
String resource = xNode.getStringAttribute("resource");
if (StringUtils.isNullOrEmpty(resource)) {
throw new ParsingException(
"Error parsing ddal-config XML . Cause: tableRule attribute 'resource' required.");
}
InputStream source = Utils.getResourceAsStream(resource);
if (source == null) {
throw new ParsingException("Can't load the table rule resource " + resource);
}
new XmlRuleConfigParser(source, configuration).parse();
}
}
private void parseRuleAlgorithms(List<XNode> xNodes) {
for (XNode xNode : xNodes) {
RuleAlgorithmConfig config = new RuleAlgorithmConfig();
String name = xNode.getStringAttribute("name");
String clazz = xNode.getStringAttribute("class");
if (StringUtils.isNullOrEmpty(name)) {
throw new ParsingException("ruleAlgorithm attribute 'name' is required.");
}
if (StringUtils.isNullOrEmpty(clazz)) {
throw new ParsingException("ruleAlgorithm attribute 'clazz' is required.");
}
config.setName(name);
config.setClazz(clazz);
config.setProperties(xNode.getChildrenAsProperties());
Object ruleAlgorithm = constructAlgorithm(config);
configuration.addRuleAlgorithm(name, ruleAlgorithm);
}
}
private Object constructAlgorithm(RuleAlgorithmConfig algorithmConfig) {
String clazz = algorithmConfig.getClazz();
Properties properties = algorithmConfig.getProperties();
BeanInfo beanInfo = null;
try {
Object ruleAlgorithm = Class.forName(clazz).newInstance();
beanInfo = Introspector.getBeanInfo(ruleAlgorithm.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String propertyValue = properties.getProperty(propertyDescriptor.getName());
if (propertyValue != null) {
setPropertyWithAutomaticType(ruleAlgorithm, propertyDescriptor, propertyValue);
}
}
return beanInfo;
} catch (InvocationTargetException e) {
throw new ParsingException("There was an error to construct RuleAlgorithm " + clazz
+ " Cause: " + e.getTargetException(), e);
} catch (Exception e) {
throw new ParsingException(
"There was an error to construct RuleAlgorithm " + clazz + " Cause: " + e, e);
}
}
/**
* @param tableConfings
* @param config
*/
private void addToListIfNotDuplicate(List<TableConfig> tableConfings, TableConfig config) {
if (tableConfings.contains(config)) {
throw new ParsingException(
"Duplicate table name '" + config.getName() + "' in schema.");
}
tableConfings.add(config);
}
private void parseSchemaConfig(XNode xNode) {
SchemaConfig dsConfig = new SchemaConfig();
String name = xNode.getStringAttribute("name");
String shard = xNode.getStringAttribute("shard");
if (StringUtils.isNullOrEmpty(name)) {
throw new ParsingException("schema attribute 'name' is required.");
}
dsConfig.setName(name);
dsConfig.setShard(shard);
List<TableConfig> tableConfings = New.arrayList();
List<XNode> xNodes = xNode.evalNodes("tableGroup");
for (XNode tableGroupNode : xNodes) {
Map<String, String> attributes = parseTableGroupAttributs(tableGroupNode);
xNodes = tableGroupNode.evalNodes("table");
for (XNode tableNode : xNodes) {
TableConfig config = newTableConfig(dsConfig, attributes, tableNode);
addToListIfNotDuplicate(tableConfings, config);
}
}
xNodes = xNode.evalNodes("table");
for (XNode tableNode : xNodes) {
TableConfig config = newTableConfig(dsConfig, null, tableNode);
addToListIfNotDuplicate(tableConfings, config);
}
dsConfig.setTables(tableConfings);
configuration.setSchemaConfig(dsConfig);
configuration.getTemporaryTableRouters().clear();
}
/**
* @param config
* @param scanLevel
*/
private void setTableScanLevel(TableConfig config, String scanLevel) {
if ("unlimited".equals(scanLevel)) {
config.setScanLevel(TableConfig.SCANLEVEL_UNLIMITED);
} else if ("filter".equals(scanLevel)) {
config.setScanLevel(TableConfig.SCANLEVEL_FILTER);
} else if ("anyIndex".equals(scanLevel)) {
config.setScanLevel(TableConfig.SCANLEVEL_ANYINDEX);
} else if ("uniqueIndex".equals(scanLevel)) {
config.setScanLevel(TableConfig.SCANLEVEL_UNIQUEINDEX);
} else if ("shardingKey".equals(scanLevel)) {
config.setScanLevel(TableConfig.SCANLEVEL_SHARDINGKEY);
}
}
/**
* @param tableNode
* @return
*/
private Map<String, String> parseTableGroupAttributs(XNode tableNode) {
Map<String, String> attributes = New.hashMap();
String shard = tableNode.getStringAttribute("shard");
String router = tableNode.getStringAttribute("router");
String validation = tableNode.getStringAttribute("validation");
String scanLevel = tableNode.getStringAttribute("scanLevel");
attributes.put("shard", shard);
attributes.put("router", router);
attributes.put("validation", validation);
attributes.put("scanLevel", scanLevel);
return attributes;
}
/**
* @param scConfig
* @param template
* @param tableNode
* @return
*/
private TableConfig newTableConfig(SchemaConfig scConfig, Map<String, String> template,
XNode tableNode) {
TableConfig config = new TableConfig();
String router = null;
boolean validation = scConfig.isValidation();
String scanLevel = "none";
String shard = null;
if (template != null) {
router = template.get("router");
String s = template.get("validation");
if (!StringUtils.isNullOrEmpty(s)) {
validation = Boolean.parseBoolean(s);
}
scanLevel = template.get("scanLevel");
shard = template.get("shard");
}
String tableName = tableNode.getStringAttribute("name");
String routerChild = tableNode.getStringAttribute("router", router);
validation = tableNode.getBooleanAttribute("validation", validation);
scanLevel = tableNode.getStringAttribute("scanLevel", scanLevel);
shard = tableNode.getStringAttribute("shard", shard);
if (StringUtils.isNullOrEmpty(shard)) {
shard = scConfig.getShard();
}
if (StringUtils.isNullOrEmpty(tableName)) {
throw new ParsingException("table attribute 'name' is required.");
}
if (!StringUtils.isNullOrEmpty(router) && !StringUtils.equals(router, routerChild)) {
throw new ParsingException(
"table's attribute 'router' can't override tableGroup's attribute 'router'.");
}
if (StringUtils.isNullOrEmpty(router) && StringUtils.isNullOrEmpty(shard)) {
throw new ParsingException(
"attribute 'shard' must be null if attribute 'router' was assigned.");
}
router = routerChild;
config.setName(tableName);
config.setValidation(validation);
setTableScanLevel(config, scanLevel);
List<String> nodes = New.arrayList();
if (!StringUtils.isNullOrEmpty(shard)) {
for (String string : shard.split(",")) {
if (!string.trim().isEmpty() && !nodes.contains(string)) {
nodes.add(string);
}
}
TableNode[] tableNodes = new TableNode[nodes.size()];
for (int i = 0; i < nodes.size(); i++) {
tableNodes[i] = new TableNode(nodes.get(i), tableName);
}
config.setShards(tableNodes);
}
if (!StringUtils.isNullOrEmpty(router)) {
TableRouter rawRouter = configuration.getTemporaryTableRouters().get(router);
if (rawRouter == null) {
throw new ParsingException("The table router '" + router + "' is not found.");
}
TableRouter tableRouter = new TableRouter(configuration);
List<TableNode> partition = rawRouter.getPartition();
List<TableNode> inited = New.arrayList(partition.size());
for (TableNode item : partition) {
String shardName = item.getShardName();
String name = item.getObjectName();
String suffix = item.getSuffix();
if (StringUtils.isNullOrEmpty(name)) {
name = tableName;
}
TableNode initedNode = new TableNode(shardName, name, suffix);
inited.add(initedNode);
}
tableRouter.setId(rawRouter.getId());
tableRouter.setPartition(inited);
RuleExpression rawExpression = rawRouter.getRuleExpression();
RuleExpression expression = new RuleExpression(tableRouter);
expression.setExpression(rawExpression.getExpression());
expression.setRuleColumns(rawExpression.getRuleColumns());
tableRouter.setRuleExpression(expression);
config.setTableRouter(tableRouter);
config.setShards(null);
}
config.setSchemaConfig(scConfig);
return config;
}
private DataSource constructDataSource(DataNodeConfig dataSourceConfig) {
String id = dataSourceConfig.getId();
DataSource dataSource;
try {
if (dataSourceConfig.isJndiDataSource()) {
String jndiName = dataSourceConfig.getJndiName();
dataSource = lookupJndiDataSource(jndiName, dataSourceConfig.getProperties());
} else {
String clazz = dataSourceConfig.getClazz();
Properties properties = dataSourceConfig.getProperties();
dataSource = (DataSource) Class.forName(clazz).newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(dataSource.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String propertyValue = properties.getProperty(propertyDescriptor.getName());
if (propertyValue != null) {
setPropertyWithAutomaticType(dataSource, propertyDescriptor, propertyValue);
}
}
}
} catch (InstantiationException e) {
throw new DataSourceException(
"There was an error to construct DataSource id = " + id + ". Cause: " + e, e);
} catch (IllegalAccessException e) {
throw new DataSourceException(
"There was an error to construct DataSource id = " + id + ". Cause: " + e, e);
} catch (ClassNotFoundException e) {
throw new DataSourceException(
"There was an error to construct DataSource id = " + id + ". Cause: " + e, e);
} catch (Exception e) {
throw new DataSourceException(
"There was an error to construct DataSource id = " + id + ". Cause: " + e, e);
}
return dataSource;
}
private DataSource lookupJndiDataSource(String jndiName, Properties jndiContext) {
DataSource dataSource = null;
try {
InitialContext initCtx = null;
initCtx = new InitialContext(jndiContext);
dataSource = (DataSource) initCtx.lookup(jndiName);
return dataSource;
} catch (NamingException e) {
throw new DataSourceException("There was an error configuring JndiDataSource.", e);
} catch (Exception e) {
throw new DataSourceException("There was an error configuring JndiDataSource.", e);
}
}
/**
* @param ruleAlgorithm
* @param pd
* @param propertyValue
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private void setPropertyWithAutomaticType(Object ruleAlgorithm, PropertyDescriptor pd,
String propertyValue) throws IllegalAccessException, InvocationTargetException {
Class<?> pType = pd.getPropertyType();
if (pType == Short.class || pType == short.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, Short.valueOf(propertyValue));
} else if (pType == Byte.class || pType == byte.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, Byte.valueOf(propertyValue));
} else if (pType == Integer.class || pType == int.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, Integer.valueOf(propertyValue));
} else if (pType == Double.class || pType == double.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, Double.valueOf(propertyValue));
} else if (pType == Float.class || pType == float.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, Float.valueOf(propertyValue));
} else if (pType == Boolean.class || pType == boolean.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, Boolean.valueOf(propertyValue));
} else if (pType == Long.class || pType == long.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, Long.valueOf(propertyValue));
} else if (pType == Character.class || pType == char.class) {
pd.getWriteMethod().invoke(ruleAlgorithm,
Character.valueOf(propertyValue.toCharArray()[1]));
} else if (pType == String.class) {
pd.getWriteMethod().invoke(ruleAlgorithm, propertyValue);
} else {
throw new IllegalArgumentException("Can't set " + ruleAlgorithm.getClass().getName()
+ "'s property " + pd.getName() + ",the type is " + pType.getName());
}
}
}