package org.opensource.clearpool.configuration; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.sql.CommonDataSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.stream.XMLInputFactory; import org.opensource.clearpool.configuration.console.Console; import org.opensource.clearpool.exception.ConnectionPoolXMLParseException; import org.opensource.clearpool.logging.PoolLogger; import org.opensource.clearpool.logging.PoolLoggerFactory; import org.opensource.clearpool.util.XMLEntityResolver; import org.opensource.clearpool.util.XMLErrorHandler; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** * This class load,check and resolve XML.You can get the details of how to resolve XML by the method * {@link #parseXML}. * * @author xionghui * @date 26.07.2014 * @version 1.0 */ public class XMLConfiguration { private static final PoolLogger LOGGER = PoolLoggerFactory.getLogger(XMLConfiguration.class); /** * encoding used to read and write xml. */ private static String encoding = "UTF-8"; /** * JAXP attribute used to configure the schema language for validation. */ private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; /** * JAXP attribute value indicating the XSD schema language. */ private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; // we can set the configuration's path by the SYSTEM_KEY private final static String SYSTEM_PATH_KEY = "clearpool.cfg.path"; // the configuration's default path private final static String DEFAULT_PATH = "/clearpool.xml"; public final static String CONSOLE = "console"; public final static String JDBC = "jdbc"; public final static String JNDI = "jndi"; private final static String ALIAS = "alias"; private final static String DISTRIBUTE_URL = "distribute-url"; private final static String JTA_SUPPORT = "jta-support"; private final static String CORE_POOL_SIZE = "core-pool-size"; private final static String MAX_POOL_SIZE = "max-pool-size"; private final static String ACQUIRE_INCREMENT = "acquire-increment"; private final static String ACQUIRE_RETRY_TIMES = "acquire-retry-times"; private final static String USELESS_CONNECTION_EXCEPTION = "useless-connection-exception"; private final static String LIMIT_IDLE_TIME = "limit-idle-time"; private final static String KEEP_TEST_PERIOD = "keep-test-period"; private final static String TEST_TABLE_NAME = "test-table-name"; private final static String TEST_BEFORE_USE = "test-before-use"; private final static String TEST_QUER_YSQL = "test-query-sql"; private final static String SHOW_SQL = "show-sql"; private final static String SQL_TIME_FILTER = "sql-time-filter"; // the public entry to {@link #Configuration}. public static Map<String, ConfigurationVO> getCfgVO(String path) { path = getRealPath(path); Map<String, ConfigurationVO> cfgMap = new HashMap<String, ConfigurationVO>(); XMLInputFactory xmlFac = XMLInputFactory.newInstance(); long begin = System.currentTimeMillis(); parseXML(cfgMap, path, xmlFac, true, false); long cost = System.currentTimeMillis() - begin; LOGGER.info("XML parsing cost " + cost + "ms"); return cfgMap; } /** * If path is null,we get path from {@code SYSTEM_KEY}. If path is not set in {@code SYSTEM_KEY} * ,we set it as {@code DEFAULT_PATH}. * * @param path the path of the XML */ private static String getRealPath(String path) { if (path == null) { path = System.getProperty(SYSTEM_PATH_KEY); if (path == null) { path = DEFAULT_PATH; } } else { path = path.trim(); } return path; } /** * If xml has {@code DISTRIBUTE_URL},we parse XML recursive. * * If reader don't has {@code DISTRIBUTE_URL},we treat the XML as a {@link ConfigurationVO}.After * we fill {@link ConfigurationVO},we check if {@link ConfigurationVO} is legal.IF * {@link ConfigurationVO} is legal,we add it to cfgMap,otherwise we throw a * {@link ConnectionPoolXMLParseException}. * * @param cfgMap is hashMap of the alias to cfgVO * @param path is the path of the cfg * @param xmlFac is used to parse path * @param isFirst if is the first cfg * @param distributed if is distributed */ private static void parseXML(Map<String, ConfigurationVO> cfgMap, String path, XMLInputFactory xmlFac, boolean isFirst, boolean distributed) { Set<String> urls = new HashSet<String>(); ConfigurationVO cfgVO = new ConfigurationVO(); // get the Reader by path. Reader reader = getResourceAsReader(path); try { Document document = createDocument(reader); Element rootElement = document.getDocumentElement(); NodeList children = rootElement.getChildNodes(); for (int i = 0, size = children.getLength(); i < size; i++) { Node childNode = children.item(i); if (childNode instanceof Element) { Element child = (Element) childNode; String nodeName = child.getNodeName(); if (CONSOLE.equals(nodeName)) { if (!isFirst) { throw new ConnectionPoolXMLParseException( CONSOLE + " should set in the first configuration"); } Console console = new Console(); console.parse(child); ConfigurationVO.setConsole(console); } else if (JDBC.equals(nodeName)) { CommonDataSource jdbcDs = JDBCConfiguration.parse(child, document, path); cfgVO.setCommonDataSource(jdbcDs); } else if (JNDI.equals(nodeName)) { CommonDataSource jndiDs = JndiConfiguration.parse(child); cfgVO.setCommonDataSource(jndiDs); } else { String nodeValue = child.getTextContent().trim(); if (DISTRIBUTE_URL.equals(nodeName)) { distributed = true; urls.add(nodeValue); } else { fillNodeValue(nodeName, nodeValue, cfgVO); } } } } } catch (Exception e) { LOGGER.error("parseXML error: ", e); throw new ConnectionPoolXMLParseException(e); } finally { try { reader.close(); } catch (IOException e) { LOGGER.error("close reader error: ", e); } } if (urls.size() > 0) { for (String url : urls) { // invoke parseXML recursive parseXML(cfgMap, url, xmlFac, false, distributed); } } else { cfgVO.init(); if (cfgMap.put(cfgVO.getAlias(), cfgVO) != null) { throw new ConnectionPoolXMLParseException("cfg's alias " + cfgVO.getAlias() + " repeat"); } } } /** * Fill normal node value. * * @param nodeName * @param nodeValue * @param cfgVO */ private static void fillNodeValue(String nodeName, String nodeValue, ConfigurationVO cfgVO) { if (ALIAS.equals(nodeName)) { cfgVO.setAlias(nodeValue); } else if (JTA_SUPPORT.equals(nodeName)) { cfgVO.setJtaSupport(Boolean.valueOf(nodeValue)); } else if (CORE_POOL_SIZE.equals(nodeName)) { cfgVO.setCorePoolSize(Integer.valueOf(nodeValue)); } else if (MAX_POOL_SIZE.equals(nodeName)) { cfgVO.setMaxPoolSize(Integer.valueOf(nodeValue)); } else if (ACQUIRE_INCREMENT.equals(nodeName)) { cfgVO.setAcquireIncrement(Integer.valueOf(nodeValue)); } else if (ACQUIRE_RETRY_TIMES.equals(nodeName)) { cfgVO.setAcquireRetryTimes(Integer.valueOf(nodeValue)); } else if (USELESS_CONNECTION_EXCEPTION.equals(nodeName)) { cfgVO.setUselessConnectionException(Boolean.valueOf(nodeValue)); } else if (LIMIT_IDLE_TIME.equals(nodeName)) { cfgVO.setLimitIdleTime(Integer.valueOf(nodeValue) * 1000L); } else if (KEEP_TEST_PERIOD.equals(nodeName)) { cfgVO.setKeepTestPeriod(Integer.valueOf(nodeValue) * 1000L); } else if (TEST_TABLE_NAME.equals(nodeName)) { cfgVO.setTestTableName(nodeValue); } else if (TEST_BEFORE_USE.equals(nodeName)) { cfgVO.setTestBeforeUse(Boolean.valueOf(nodeValue)); } else if (TEST_QUER_YSQL.equals(nodeName)) { cfgVO.setTestQuerySql(nodeValue); } else if (SHOW_SQL.equals(nodeName)) { cfgVO.setShowSql(Boolean.valueOf(nodeValue)); } else if (SQL_TIME_FILTER.equals(nodeName)) { cfgVO.setSqlTimeFilter(Integer.valueOf(nodeValue) * 1000L); } } /** * Get reader by path and {@link #encoding}. * * @param resource * @return * @throws UnsupportedEncodingException */ private static Reader getResourceAsReader(String path) { Reader reader; try { reader = new InputStreamReader(getResourceAsStream(path), encoding); } catch (UnsupportedEncodingException e) { LOGGER.error("getResourceAsReader error: ", e); throw new ConnectionPoolXMLParseException(e); } return reader; } /** * The rule of searching resource is base on ClassLoader searching rule. * * @see java.lang.Class#getResourceAsStream(String) * @param path is the url of the resource * @return the inputstream of the resource */ private static InputStream getResourceAsStream(String path) { path = path.startsWith("/") ? path.substring(1) : path; InputStream inStream; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader == null) { inStream = ClassLoader.getSystemResourceAsStream(path); } else { inStream = classLoader.getResourceAsStream(path); } if (inStream == null) { throw new ConnectionPoolXMLParseException(path + " not found"); } return inStream; } /** * Create document by reader. * * @param reader * @return * @throws Exception */ private static Document createDocument(Reader reader) throws Exception { InputSource inputSource = new InputSource(reader); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(true); factory.setNamespaceAware(true); factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(new XMLEntityResolver()); builder.setErrorHandler(new XMLErrorHandler()); return builder.parse(inputSource); } public static String getEncoding() { return encoding; } public static void setEncoding(String encoding) { XMLConfiguration.encoding = encoding; } }