package com.dinstone.rpc; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import java.util.Properties; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.SAXException; import com.dinstone.rpc.serialize.SerializeType; public class Configuration { private static final Logger LOG = LoggerFactory.getLogger(Configuration.class); /** service host name */ private static final String SERVICE_HOST = "rpc.service.host"; /** service port */ private static final String SERVICE_PORT = "rpc.service.port"; /** RPC protocol max length */ private static final String MESSAGE_MAXLENGTH = "rpc.message.maxlength"; /** serialize type */ private static final String SERIALIZE_TYPE = "rpc.serialize.type"; /** Prefix for system property placeholders: "${" */ private static final String PLACEHOLDER_PREFIX = "${"; /** Suffix for system property placeholders: "}" */ private static final String PLACEHOLDER_SUFFIX = "}"; protected final Properties properties = new Properties(); /** * */ public Configuration() { } /** * */ public Configuration(String configLocation) { if (configLocation == null) { throw new IllegalArgumentException("configLocation is null"); } InputStream stream = getResourceStream(configLocation); if (stream == null) { throw new IllegalArgumentException("can't find out configuration [" + configLocation + "] from classpath."); } loadConfiguration(stream); } public Configuration(Configuration config) { this.properties.putAll(config.properties); } public void writeConfiguration(OutputStream out) { try { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); Element conf = doc.createElement("configuration"); doc.appendChild(conf); for (Enumeration<?> e = properties.keys(); e.hasMoreElements();) { String name = (String) e.nextElement(); Object object = properties.get(name); String value = null; if (object instanceof String) { value = (String) object; } else { continue; } Element propNode = doc.createElement("property"); conf.appendChild(propNode); Element nameNode = doc.createElement("name"); nameNode.appendChild(doc.createTextNode(name)); propNode.appendChild(nameNode); Element valueNode = doc.createElement("value"); valueNode.appendChild(doc.createTextNode(value)); propNode.appendChild(valueNode); } TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = transFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(out); transformer.transform(source, result); } catch (Exception e) { throw new RuntimeException(e); } } public void loadConfiguration(InputStream in) { if (in == null) { throw new IllegalArgumentException("inputstream is null"); } try { this.properties.putAll(loadResource(in)); } catch (Exception e) { throw new IllegalStateException("can't load configuration from inputstream.", e); } } /** * @param resource * @return */ private static InputStream getResourceStream(String resource) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader == null) { classLoader = Configuration.class.getClassLoader(); } return classLoader.getResourceAsStream(resource); } private static Properties loadResource(InputStream in) throws IOException, ParserConfigurationException, SAXException { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); // ignore all comments inside the xml file builderFactory.setIgnoringComments(true); // builderFactory.setCoalescing(true); builderFactory.setIgnoringElementContentWhitespace(true); builderFactory.setNamespaceAware(true); // allow includes in the xml file try { builderFactory.setXIncludeAware(true); } catch (UnsupportedOperationException e) { LOG.error("Failed to set setXIncludeAware(true) for parser " + builderFactory + ":" + e, e); } DocumentBuilder builder = builderFactory.newDocumentBuilder(); Document doc = null; try { doc = builder.parse(in); } finally { if (in != null) { in.close(); } } Element root = doc.getDocumentElement(); if (!"configuration".equals(root.getTagName())) { LOG.error("bad config file: top-level element not <configuration>"); throw new IllegalStateException("bad config file: top-level element not <configuration>"); } Properties properties = new Properties(); parseResource(properties, root); return properties; } /** * @param properties * @param root */ private static void parseResource(Properties properties, Element root) { NodeList props = root.getChildNodes(); for (int i = 0; i < props.getLength(); i++) { Node propNode = props.item(i); if (!(propNode instanceof Element)) { continue; } Element prop = (Element) propNode; if ("configuration".equals(prop.getTagName())) { parseResource(properties, prop); continue; } if (!"property".equals(prop.getTagName())) { LOG.warn("bad config file: element not <property>,skip this element {}", prop); continue; } NodeList fields = prop.getChildNodes(); String attr = null; String value = null; for (int j = 0; j < fields.getLength(); j++) { Node fieldNode = fields.item(j); if (!(fieldNode instanceof Element)) { continue; } Element field = (Element) fieldNode; if ("name".equals(field.getTagName()) && field.hasChildNodes()) { attr = ((Text) field.getFirstChild()).getData().trim(); } if ("value".equals(field.getTagName()) && field.hasChildNodes()) { value = ((Text) field.getFirstChild()).getData(); value = resolvePlaceholders(value); } } if (attr != null && value != null) { properties.setProperty(attr, value); } } } /** * Resolve ${...} placeholders in the given text, replacing them with corresponding system property values. * * @param text * the String to resolve * @return the resolved String * @see #PLACEHOLDER_PREFIX * @see #PLACEHOLDER_SUFFIX */ private static String resolvePlaceholders(String text) { StringBuilder buf = new StringBuilder(text); int startIndex = buf.indexOf(PLACEHOLDER_PREFIX); while (startIndex != -1) { int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length()); if (endIndex != -1) { String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length(); try { String propVal = System.getProperty(placeholder); if (propVal == null) { // Fall back to searching the system environment. propVal = System.getenv(placeholder); } if (propVal != null) { buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal); nextIndex = startIndex + propVal.length(); } else { System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text + "] as system property: neither system property nor environment variable found"); } } catch (Throwable ex) { System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text + "] as system property: " + ex); } startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex); } else { startIndex = -1; } } return buf.toString(); } /** * Get the value of the <code>name</code> property, <code>null</code> if no such property exists. * * @param name * @return */ public String get(String name) { return properties.getProperty(name); } /** * Get the value of the <code>name</code> property, <code>defaultValue</code> if no such property exists. * * @param name * @param defaultValue * @return */ public String get(String name, String defaultValue) { String ret = properties.getProperty(name); return ret == null ? defaultValue : ret; } /** * Set the <code>value</code> of the <code>name</code> property. * * @param name * property name. * @param value * property value. */ public void set(String name, String value) { properties.setProperty(name, value); } /** * Get the value of the <code>name</code> property as an <code>int</code>. If no such property exists, or if the * specified value is not a valid <code>int</code>, then <code>defaultValue</code> is returned. * * @param name * @param defaultValue * @return */ public int getInt(String name, int defaultValue) { String valueString = get(name); if (valueString == null) { return defaultValue; } try { String hexString = getHexDigits(valueString); if (hexString != null) { return Integer.parseInt(hexString, 16); } return Integer.parseInt(valueString); } catch (NumberFormatException e) { return defaultValue; } } /** * Set the value of the <code>name</code> property to an <code>int</code>. * * @param name * property name. * @param value * <code>int</code> value of the property. */ public void setInt(String name, int value) { set(name, Integer.toString(value)); } /** * Get the value of the <code>name</code> property as a <code>long</code>. If no such property is specified, or if * the specified value is not a valid <code>long</code>, then <code>defaultValue</code> is returned. * * @param name * property name. * @param defaultValue * default value. * @return property value as a <code>long</code>, or <code>defaultValue</code>. */ public long getLong(String name, long defaultValue) { String valueString = get(name); if (valueString == null) { return defaultValue; } try { String hexString = getHexDigits(valueString); if (hexString != null) { return Long.parseLong(hexString, 16); } return Long.parseLong(valueString); } catch (NumberFormatException e) { return defaultValue; } } /** * Set the value of the <code>name</code> property to a <code>long</code>. * * @param name * property name. * @param value * <code>long</code> value of the property. */ public void setLong(String name, long value) { set(name, Long.toString(value)); } private String getHexDigits(String value) { boolean negative = false; String str = value; String hexString = null; if (value.startsWith("-")) { negative = true; str = value.substring(1); } if (str.startsWith("0x") || str.startsWith("0X")) { hexString = str.substring(2); if (negative) { hexString = "-" + hexString; } return hexString; } return null; } /** * Get the value of the <code>name</code> property as a <code>float</code>. If no such property is specified, or if * the specified value is not a valid <code>float</code>, then <code>defaultValue</code> is returned. * * @param name * property name. * @param defaultValue * default value. * @return property value as a <code>float</code>, or <code>defaultValue</code>. */ public float getFloat(String name, float defaultValue) { String valueString = get(name); if (valueString == null) { return defaultValue; } try { return Float.parseFloat(valueString); } catch (NumberFormatException e) { return defaultValue; } } /** * Set the value of the <code>name</code> property to a <code>float</code>. * * @param name * property name. * @param value * property value. */ public void setFloat(String name, float value) { set(name, Float.toString(value)); } /** * Get the value of the <code>name</code> property as a <code>boolean</code> . If no such property is specified, or * if the specified value is not a valid <code>boolean</code>, then <code>defaultValue</code> is returned. * * @param name * property name. * @param defaultValue * default value. * @return property value as a <code>boolean</code>, or <code>defaultValue</code>. */ public boolean getBoolean(String name, boolean defaultValue) { String valueString = get(name); if ("true".equals(valueString)) { return true; } else if ("false".equals(valueString)) { return false; } else { return defaultValue; } } public String getServiceHost() { return get(SERVICE_HOST); } public void setServiceHost(String host) { set(SERVICE_HOST, host); } public int getServicePort() { return getInt(SERVICE_PORT, 9958); } public void setServicePort(int port) { setInt(SERVICE_PORT, port); } public int getMessageMaxSize() { return getInt(MESSAGE_MAXLENGTH, Integer.MAX_VALUE); } public void setSerializeType(SerializeType type) { setInt(SERIALIZE_TYPE, type.getValue()); } public SerializeType getSerializeType() { return SerializeType.valueOf(getInt(SERIALIZE_TYPE, SerializeType.JACKSON.getValue())); } public int getCallTimeout() { return getInt("rpc.call.timeout", 3000); } }