package com.lizard.fastdb.config; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import com.lizard.fastdb.datasource.DataSourceException; import com.lizard.fastdb.dialect.Dialect; import com.lizard.fastdb.io.ClasspathResourceResolver; import com.lizard.fastdb.io.ResourceResolver; import com.lizard.fastdb.util.ClassLoaderUtils; import com.lizard.fastdb.util.ObjectExtend; import com.lizard.fastdb.util.XMLUtils; /** * 数据源配置解析类,用于解析给定的数据源配置文件(V1版为datasource.xml) * * @author SHEN.GANG */ public class Config { private static final Log logger = LogFactory.getLog(Config.class); // 数据源配置文件格式 private static final String DATASOURCE_SCHEMA = "META-INF/fastdb-1.0.xsd"; // 默认数据源属性配置 private static final String DATASOURCE_DEFAULT_PROP = "META-INF/datasource-default.properties"; // 默认数据源配置属性集,在类被载入时集合初始化,可被其他地方调用 public static final Properties DEFAULT_PROP = new Properties(); // 已加载的配置文件路径集,用于防止配置文件重复加载 private static final Set<String> LOADED_CONFIG_PATH = new LinkedHashSet<String>(); // 最终的数据源列表 private List<Properties> datasourceList = null; // 最终的软连接列表 private Map<String, String> linkmapping = null; // 用于获取类路径下的资源 private ResourceResolver resolver = null; // 加载数据源默认配置 static { loadDefaultConfig(); } /** * 解析指定的数据源配置 * * @param path 数据源配置文件路径 */ public Config(String path) { datasourceList = new LinkedList<Properties>(); linkmapping = new HashMap<String, String>(); resolver = new ClasspathResourceResolver(); // 解析指定匹配模式的文件 parsePatternPath(path); // 验证软连接合法性 validateLinkmaping(); } /** * 加载数据源默认配置 */ private static void loadDefaultConfig() { logger.info("Loaded datasource default configuration from: " + DATASOURCE_DEFAULT_PROP); InputStream is = ClassLoaderUtils.getResourceAsStream(DATASOURCE_DEFAULT_PROP, Config.class); if (is == null) { logger.error("Can't get " + DATASOURCE_DEFAULT_PROP + " inputstream."); throw new DataSourceException("Can't get " + DATASOURCE_DEFAULT_PROP + " inputstream."); } else { try { DEFAULT_PROP.load(is); } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); is = null; } catch (IOException e) { e.printStackTrace(); } } } if (logger.isDebugEnabled()) { logger.debug("Default datasource configure: [" + DEFAULT_PROP + "]"); } } /** * 解析匹配指定模式的文件 * * @param pattern_path 文件名匹配模式 */ private void parsePatternPath(String pattern_path) { // 从classpath下获得所有匹配文件名模式的文件url Set<URL> resources = resolver.getResources(pattern_path); if (logger.isDebugEnabled()) { logger.debug("Find resources by [" + pattern_path + "]: " + resources); } if (resources == null || resources.size() == 0) { return; } for (URL resource : resources) { // 解析数据源配置文件 parseXml(resource); } } /** * 递归解析数据源xml配置文件 * * @param url 当前xml文件url */ private void parseXml(URL resource) { // 1、获取xml Document doc = loadXml(resource); if (doc == null) { return; } Element root = doc.getRootElement(); // 2、解析constants配置 Properties cons = parseConstants(root); // 3、解析datasource配置 List<Properties> dsList = parseDataSources(root); // 4、合并constant和datasource mergeConfigParams(cons, dsList); // 5、解析link-mapping parseLinkmapping(root); // 6、解析include parseInclude(root); } /** * 从指定路径加载xml,并返回Document对象 * * @param url xml文件url * @return Document对象 */ private Document loadXml(URL xmlUrl) { Document doc = null; InputStream is = null; try { logger.info("Loaded datasource configuration from: " + xmlUrl); // 检查当前配置文件是否已经被加载过 if (LOADED_CONFIG_PATH.contains(xmlUrl.getPath())) { logger.warn("Datasource config file [" + xmlUrl + "] has been loaded."); return null; } LOADED_CONFIG_PATH.add(xmlUrl.getPath()); is = xmlUrl.openStream(); doc = XMLUtils.getDocumentFromStream(is); } catch (IOException e) { logger.error("Can't load datasource config file from: " + xmlUrl + "!", e); e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } is = null; } } // 对数据源配置文件使用datasource schema文件进行合法性验证 if (doc != null && !XMLValidator.validate(DATASOURCE_SCHEMA, doc.asXML())) { logger.error("Datasource config file [" + xmlUrl + "] is invalid!"); throw new DataSourceException("DataSource config file [" + xmlUrl + "] is invalid!"); } return doc; } /** * 解析常量配置 * * @param root 配置文件根元素 */ @SuppressWarnings("unchecked") private Properties parseConstants(Element root) { // 由于数据源配置文件增加了命名空间等,下面所有的xpath必须进行namespace设置,否则无法获取节点信息 Element constants = (Element) root.selectSingleNode(XMLUtils.setXPathNamespace(root, "./constants"));// 常量集 Properties prop = new Properties(); if (constants == null) { if (logger.isDebugEnabled()) { logger.debug("Constant configure: []"); } return prop; } List<Element> constantList = constants.selectNodes(XMLUtils.setXPathNamespace(constants, "./constant")); if (constantList == null || constantList.size() == 0) { if (logger.isDebugEnabled()) { logger.debug("Constant configure: []"); } return prop; } Attribute nameA = null; Attribute valueA = null; String name = null; String value = null; for (Element constant : constantList) { nameA = constant.attribute("name"); if (nameA == null) { throw new DataSourceException("Couldn't find [name] attribute in [constant] element!"); } valueA = constant.attribute("value"); if (valueA == null) { throw new DataSourceException("Couldn't find [value] attribute in [constant] element!"); } name = nameA.getStringValue().trim(); value = valueA.getStringValue().trim(); prop.setProperty(name.toLowerCase(), value); } nameA = null; valueA = null; name = null; value = null; if (logger.isDebugEnabled()) { logger.debug("Constant configure: [" + prop + "]"); } return prop; } /** * 加载所有数据源配置 * * @param root 配置文件根元素 * @return 数据源properties配置 */ @SuppressWarnings("unchecked") private List<Properties> parseDataSources(Element root) { // 获取所有数据源元素对象 List<Element> datasources = root.selectNodes(XMLUtils.setXPathNamespace(root, "./datasource")); if (datasources == null || datasources.size() == 0) { return null; } List<Properties> propList = new ArrayList<Properties>(); for (Element dsE : datasources) { propList.add(parseDataSource(dsE)); } return propList; } /** * 加载指定数据源 * * @param datasource 数据源配置根元素 * @return 数据源properties格式配置 */ @SuppressWarnings("unchecked") private Properties parseDataSource(Element datasource) { Properties prop = new Properties(); List<Element> elements = datasource.selectNodes(XMLUtils.setXPathNamespace(datasource, "./*")); if (elements == null || elements.size() == 0) { throw new DataSourceException("Couldn't find any element in datasource element."); } String name = null; String value = null; for (Element ele : elements) { name = ele.getName(); // 如果是自定义元素 if ("customize".equals(name)) { parseCustomize(ele, prop); continue; } value = ele.getStringValue().trim(); prop.setProperty(name, value); } name = null; value = null; if (logger.isDebugEnabled()) { logger.debug("Datasource configure before merge: [" + prop + "]"); } return prop; } /** * 解析customize元素配置 * * @param customize customize元素 * @param prop 当前数据源配置集 */ @SuppressWarnings("unchecked") private void parseCustomize(Element customize, Properties prop) { List<Element> customizeElements = customize.selectNodes("./*"); if (customizeElements == null || customizeElements.size() == 0) { return; } String name = null; String value = null; for (Element customizeEle : customizeElements) { name = customizeEle.getName(); value = customizeEle.getStringValue().trim(); prop.setProperty(name, value); } name = null; value = null; } /** * 合并constant和datasource配置 * * @param cons constant配置 * @param dsList datasource配置列表 */ private void mergeConfigParams(Properties cons, List<Properties> dsList) { if (dsList == null || dsList.size() == 0) { return; } for (Properties ds : dsList) { // 数据源重复配置判断 String exist_dsname = isDataSourceRepeated(ds); // 当前数据源名称 String name = ds.getProperty("name"); // 不重复 if (exist_dsname == null) { // 查看数据源名字是否存在 if (isDataSourceNameExist(name)) { logger.error("Datasource named [" + name + "] is exist!"); throw new DataSourceException("DataSource named [" + name + "] is exist!"); } // 加入db-dialect initDSDialect(ds); ds = ObjectExtend.extend(Properties.class, DEFAULT_PROP, cons, ds); if (logger.isDebugEnabled()) { logger.debug("Datasource configure after merge: " + ds); } datasourceList.add(ds); addLinkmapping(name, name); } else { logger.info("DataSource [" + exist_dsname + "] is duplicate to DataSource [" + name + "]! We make the softlink [" + name + " --> " + exist_dsname + "] to alternative it!"); addLinkmapping(name, exist_dsname); } } } /** * 判断数据源是否重复<br> * 通过driver-url、user、password判断是否重复 * * @param source 源数据源 * @return 重复的数据源名称,没有重复的数据源则返回null */ private String isDataSourceRepeated(Properties source) { String driver_url = source.getProperty("driver-url"); String user = source.getProperty("user"); String password = source.getProperty("password"); // 去除url后面的参数 int index = driver_url.lastIndexOf("?"); if (index != -1) { driver_url = driver_url.substring(0, index); } for (Properties ds : datasourceList) { String ds_driver_url = ds.getProperty("driver-url"); String ds_user = ds.getProperty("user"); String ds_password = ds.getProperty("password"); index = ds_driver_url.lastIndexOf("?"); if (index != -1) { ds_driver_url = ds_driver_url.substring(0, index); } // 数据源相同的判断条件是:user、password、url 三者完全相同 if (driver_url.equalsIgnoreCase(ds_driver_url) && user.equalsIgnoreCase(ds_user) && password.equalsIgnoreCase(ds_password)) { return ds.getProperty("name"); } } return null; } /** * 判断数据源名称是否重复,忽略大小写 * * @param name 数据源名称 * @return true -- 重复,false -- 不重复 */ private boolean isDataSourceNameExist(String name) { for (Properties ds : datasourceList) { if (ds.getProperty("name").equalsIgnoreCase(name)) { return true; } } return false; } /** * 根据数据源的 driver-class 设置数据源的 db-dialect * * @param prop 当前数据源 */ private void initDSDialect(Properties prop) { if (prop.getProperty("driver-class").indexOf("mysql") != -1) { prop.setProperty("db-dialect", Dialect.MYSQL); } else if (prop.getProperty("driver-class").indexOf("oracle") != -1) { prop.setProperty("db-dialect", Dialect.ORACLE); } // TODO 此处为兼容权限系统1版,权限系统升级后可移除 prop.setProperty("dbdialect", prop.getProperty("db-dialect")); } /** * 解析软连接配置 * * @param root 配置文件根元素 */ @SuppressWarnings("unchecked") private void parseLinkmapping(Element root) { List<Element> linkmappings = root.selectNodes(XMLUtils.setXPathNamespace(root, "./link-mapping")); if (linkmappings == null || linkmappings.size() == 0) { return; } Attribute nameA = null; Attribute linktoA = null; String name = null; String linkto = null; for (Element lm : linkmappings) { nameA = lm.attribute("name"); if (nameA == null) { throw new DataSourceException("Couldn't find [name] attribute in [link-mapping] element!"); } linktoA = lm.attribute("linkto"); if (linktoA == null) { throw new DataSourceException("Couldn't find [linkto] attribute in [link-mapping] element!"); } name = nameA.getStringValue(); linkto = linktoA.getStringValue(); addLinkmapping(name, linkto); } nameA = null; linktoA = null; name = null; linkto = null; } /** * 增加软连接 * * @param name 软连接名称 * @param linkto 数据源名称 */ private void addLinkmapping(String name, String linkto) { // 去除首尾空格并转换为小写 name = (name == null ? null : name.trim().toLowerCase()); linkto = (linkto == null ? null : linkto.trim().toLowerCase()); if (name == null || "".equals(name) || linkto == null || "".equals(linkto)) { logger.error("link-mapping name and linkto should not be empty!"); return; } // 软连接名称已经存在,则提示替换的警告信息 if (linkmapping.containsKey(name)) { logger.warn("link-mapping name [" + name + "] is exist, replace linkmapping from [" + name + " ->" + linkmapping.get(name) + "] to [" + name + " -> " + linkto + "]."); } if (logger.isDebugEnabled()) { logger.debug("Add linkmapping configure: [name:" + name + ", linkto:" + linkto + "]"); } linkmapping.put(name, linkto); } /** * 解析include元素 * * @param root 配置文件根元素 */ @SuppressWarnings("unchecked") private void parseInclude(Element root) { // 获得所有引用数据源配置 List<Element> includes = root.selectNodes(XMLUtils.setXPathNamespace(root, "./include")); if (includes == null || includes.size() == 0) { return; } for (Element include : includes) { Attribute dsA = include.attribute("datasource"); if (dsA == null) { throw new DataSourceException("Couldn't find attribute [datasource] in [include] element!"); } String dspath = dsA.getStringValue(); if (dspath == null || "".equals(dspath.trim())) { continue; } // include中的datasource属性支持按逗号分隔多个匹配模式的书写方式 for (String path : dspath.split(",")) { if (path == null || "".equals(path.trim())) { continue; } parsePatternPath(path); } } } /** * 验证软连接指向的数据源是否是真实数据源 */ private void validateLinkmaping() { for (Map.Entry<String, String> entry : linkmapping.entrySet()) { String name = entry.getKey(); String linkto = entry.getValue(); if (!name.equals(linkto)) { String final_linkto = getLinkmappingFinalLinkto(name); // 如果final_linkto为空,表示没有找到对应的数据源 if (final_linkto == null) { logger.error("Can't find named [" + linkto + "] datasource config by link-mapping!"); throw new DataSourceException("Can't find named [" + linkto + "] datasource config by link-mapping!"); } // 如果不相等表示当前linkto并不是最终指向的数据源 if (!final_linkto.equals(linkto)) { logger.warn("Replace [" + name + "] linkto from [" + linkto + "] to [" + final_linkto + "]."); linkto = final_linkto; entry.setValue(final_linkto); } } // 连接的数据源必须是真实数据源 if (!isDataSourceNameExist(linkto)) { logger.error("Linkto [" + linkto + "] should be a real datasource!"); throw new DataSourceException("Linkto [" + linkto + "] should be a real datasource!"); } } if (logger.isDebugEnabled()) { logger.debug("Linkmapping configure: " + linkmapping); } } /** * 获取软连接name最终指向的linkto * * @param name 软连接name * @return 最终指向 */ private String getLinkmappingFinalLinkto(String name) { // 存放已遍历的名称,防止死循环 Set<String> names = new LinkedHashSet<String>(); names.add(name); String linkto = linkmapping.get(name); while (linkto != null && !linkto.equals(name)) { // 如果当前获得的名称已经在 names 中存在,则存在死循环 if (names.contains(linkto)) { throw new RuntimeException("Linkmapping config contain a dead loop -- " + names); } names.add(linkto); name = linkto; linkto = linkmapping.get(name); } names.clear(); names = null; return linkto; } /** * 获得所有解析的数据源配置 * * @return 数据源配置列表 */ public List<Properties> getDatasources() { return datasourceList; } /** * 获得所有软连接配置 * * @return 软连接配置集合 */ public Map<String, String> getLinkmapping() { return linkmapping; } }