/*******************************************************************************
* Copyright © 2012-2015 eBay Software Foundation
* This program is dual licensed under the MIT and Apache 2.0 licenses.
* Please see LICENSE for more information.
*******************************************************************************/
package com.ebay.jetstream.config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.LogManager;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import com.ebay.jetstream.util.CommonUtils;
public class ConfigUtils {
public static class StreamHolder implements FactoryBean {
private final InputStream m_stream;
@SuppressWarnings("deprecation")
// deprecated, but we'll use it for now anyway
public StreamHolder(String contents) {
m_stream = new java.io.StringBufferInputStream(contents);
}
public Object getObject() throws Exception {
return m_stream;
}
public Class<?> getObjectType() {
return InputStream.class;
}
public boolean isSingleton() {
return false;
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigUtils.class.getName());
private static final String PKGS = "java.protocol.handler.pkgs";
/**
* Adds a package to the list of URL protocol supporters. Each protocol must be in a separate package with the name of
* the protocol below the given classes package, and must contain a Handler that extends URLStreamHandler.
*
* @param location
* the class whose package is added as a root for URLStreamHandlers.
*
* @return true iff the location was added, else false.
*/
public static boolean addURLProtocols(Class<?> location) {
String ln = location.getPackage().getName();
String p = System.getProperty(PKGS);
boolean add = p == null || !p.contains(ln);
if (add) {
p = (CommonUtils.isEmptyTrimmed(p) ? "" : p + "|") + ln;
System.setProperty(PKGS, p);
}
return add;
}
public static Class<?> getClassForName(String name) {
Class<?> result = null;
try {
result = name == null ? null : Class.forName(name);
}
catch (ClassNotFoundException e) { // NOPMD
// Ignored
}
return result;
}
/**
* Gets a ConfigDataSource for the given path. It could be from a URL, from a file, from the classpath, directory, or
* from ldap (eventually). Note that the data source could represent multiple items (e.g. in the case of a folder).
*
* @param path
* the path to the datasource.
* @return the ConfigDataSource
* @throws ConfigException
* in case of any problem getting the data source.
*/
public static ConfigDataSource getConfigDataSource(String path) throws ConfigException {
if (new File(path).isDirectory())
return new ConfigDirectory(path, null);
return new ConfigFile(path);
}
/**
* Gets an individual ConfigStream (an InputStream and its location string) for the given path. The path reference
* must be to a single item, not a folder.
*
* @param path
* the path to the config stream.
* @return the ConfigStream
* @throws ConfigException
* in case of any problem, including if path is a folder.
*/
public static ConfigDataSource.ConfigStream getConfigStream(String path) throws ConfigException {
ConfigDataSource source = getConfigDataSource(path);
try {
if (source.isFolder())
throw new ConfigException("more than one stream found");
}
catch (IOException e) {
throw new ConfigException("cannot get stream: " + e, e);
}
Iterator<ConfigDataSource.ConfigStream> ii = source.iterator();
return ii.hasNext() ? ii.next() : null;
}
/**
* Expands a string with an initial variable. The form of the string should be "${VARIABLE}remainder", where VARIABLE
* is either a System property or an environment variable (checked in that order).
*
* @param theString
* @return the string with any initial property expanded
*/
public static String getInitialPropertyExpanded(String theString) {
Pattern pattern = Pattern.compile("\\$\\{([A-Z_a-z0-9.]+)\\}");
Matcher matcher = pattern.matcher(theString);
while (matcher.find()) {
String strVar = matcher.group(1);
String strValue = System.getProperty(strVar);
if (strValue == null)
strValue = System.getenv(strVar);
if (strValue == null)
throw new RuntimeException("string value not available for '" + theString + "'");
theString = matcher.replaceFirst(strValue.replaceAll("\\\\", "/"));
matcher = pattern.matcher(theString);
}
return theString;
}
/**
* Gets a property from a java Properties, or a system property or process environment variable value, in that order.
* If none is found, null is returned.
*
* @param properties
* the Properties object
* @param key
* the string name of the property or environment variable.
* @return the property or environment value, or null if none was found.
*/
public static String getPropOrEnv(Properties properties, String key) {
String it = properties.getProperty(key);
return it == null ? getPropOrEnv(key) : it;
}
/**
* Gets a java system property or process environment variable value, in that order. If none is found, null is
* returned.
*
* @param key
* the string name of the property or environment variable.
* @return the property or environment value, or null if none was found.
*/
public static String getPropOrEnv(String key) {
String it = System.getProperty(key);
if (it == null)
it = System.getenv(key);
return it;
}
public static Object getValueForType(Class<?> type, String value) throws ConfigException {
if (String.class.isAssignableFrom(type))
return value;
if (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type))
return Long.decode(value);
if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type))
return Integer.decode(value);
if (Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type))
return Double.valueOf(value);
if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type))
return Boolean.valueOf(value);
throw new ConfigException("Cannot convert " + value + " to " + type.getName());
}
/**
* Sets properties for java.util.logging.
*
* @param theProperties
* the logging properties.
*/
public static void setLoggingProperties(Properties theProperties) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ByteArrayInputStream is;
byte[] buffer;
theProperties.store(os, "LoggingProperties");
buffer = os.toByteArray();
is = new ByteArrayInputStream(buffer);
LogManager.getLogManager().readConfiguration(is);
}
public static void setProperties(Object theTarget, Properties theProperties) throws ConfigException {
Class<?> clazz = theTarget.getClass();
Method[] methods = clazz.getMethods();
for (Object key : theProperties.keySet()) {
if (!(key instanceof String))
continue;
String property = (String) key;
String setter = "set" + (property.startsWith("is") ? property.substring(2) : property);
for (int i = 0; i < methods.length; i++) {
Class<?>[] formals = methods[i].getParameterTypes();
if (formals.length == 1 && setter.equalsIgnoreCase(methods[i].getName())) {
Object value = getValueForType(formals[0], theProperties.getProperty(property));
try {
// Pretty dumb now - doesn't handle overloads or many type conversions
methods[i].invoke(theTarget, new Object[] { value });
i = methods.length;
setter = null;
}
catch (Throwable t) {
throw new ConfigException("Cannot set property " + property + " to value " + value, t);
}
} // if a matching setter
} // for methods
if (setter != null)
LOGGER.warn( "No such property found: " + key + " in " + theTarget);
} // for property keys
}
/**
* Sets the System properties for those given to the new values, and adds properties that do not exist.
*
* @param theProperties
* the set of properties to add and update.
*/
public static void setSystemProperties(Properties theProperties) {
for (Map.Entry<Object, Object> entry : theProperties.entrySet())
System.setProperty(entry.getKey().toString(), entry.getValue().toString());
}
/**
* Sets the System properties for those given to the new values, and adds properties that do not exist. Expands any
* initial dollar-bracket-name-bracket sequence with its environment variable or pre-existing system property value.
*
* @param theProperties
* the set of properties to add and update.
*/
public static void setSystemPropertiesExpanded(Properties theProperties) {
for (Map.Entry<Object, Object> entry : theProperties.entrySet())
System.setProperty(entry.getKey().toString(), getInitialPropertyExpanded(entry.getValue().toString()));
}
/**
* Writes a text file from a list of lines to a file by the given name and path. The path can optionally be prefixed
* with a ${system.property}.
*
* @param theName
* the file name, with optional System property prefix.
* @param theContents
* List of content lines.
* @throws IOException
*/
public static void writeTextFile(String theName, List<String> theContents) throws IOException {
theName = getInitialPropertyExpanded(theName);
File file = new File(theName);
if (file.isFile() && !file.delete()) {
throw new IOException("Failed to delete " + file.getCanonicalFile());
}
PrintWriter writer = new PrintWriter(file);
try {
for (Iterator<String> ii = theContents.iterator(); ii.hasNext();)
writer.println(ii.next());
}
finally {
writer.close();
}
}
}