/**
* Copyright (c) 2008--2015 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.satellite.search.config;
import com.redhat.satellite.search.config.translator.TranslatorRegistry;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeSet;
/**
* The Config class acts as an abstraction layer between our configuration
* system, and the actual implementation. The basic idea is that there is a
* global config, but you can instantiate one of your own if you want. This
* layer insulates us from changing the underlying implementation.
* <p>
* Config files are properties, with /usr/share/rhn/config-defaults/*.conf
* setting defaults that can be overridden by /etc/rhn/rhn.conf.
*
* @version $Rev$
*/
public class Configuration {
private static Logger logger = Logger.getLogger(Configuration.class);
//
// Location of config files
//
/**
* The default directory in which to look for config files
*/
public static final String DEFAULT_CONF_DIR = "/etc/rhn";
/**
* The directory in which to look for default config values
*/
public static final String DEFAULT_DEFAULT_CONF_DIR = "/usr/share/rhn/config-defaults";
/**
* The system property containing the Spacewalk configuration directory.
* If the property is not set, config files are read
* from {@link #DEFAULT_CONF_DIR}
*/
private static final String RHN_CONF_DIR_PROPERTY = "rhn.config.dir";
/**
* The system property containing the SearchServer configuration directory.
* If the property is not set, config files are read
* from /usr/share/rhn/config-defaults
*/
private static final String SEARCH_CONF_DIR_PROPERTY = "search.config.dir";
/**
* List of values that are considered true, ignoring case.
*/
private static final String[] TRUE_VALUES = {"1", "y", "true", "yes", "on"};
/**
* array of prefix in the order they should be search
* if the given lookup string is without a namespace.
*/
private String[] prefixOrder = new String[] {"web", "server"};
/** hash of configuration properties */
private Properties configValues = new Properties();
private List<KeyTranslator> translators;
/** set of configuration file names */
private TreeSet<File> fileList = new TreeSet<File>(new Comparator<File>() {
/** {inheritDoc} */
public int compare(File f1, File f2) {
// Need to make sure we read the child namespace before the base
// namespace. To do that, we sort the list in reverse order based
// on the length of the file name. If two filenames have the same
// length, then we need to do a lexigraphical comparison to make
// sure that the filenames themselves are different.
int lenDif = f2.getAbsolutePath().length() - f1.getAbsolutePath().length();
if (lenDif != 0) {
return lenDif;
}
return f2.compareTo(f1);
}
});
/**
* public constructor. Rereads config entries every time it is called.
*
* @throws ConfigException error from the Configuration layers. the jakarta
* commons conf system just throws Exception, which makes it hard to react.
* sometimes it is an IOExceptions, sometimes a SAXParserException,
* sometimes a VindictiveException. so we just turn them into our own
* exception type and toss them up. as we discover ones we might
* meaningfully want to react to, we can specialize ConfigException and catch
* those
*/
public Configuration() throws ConfigException {
translators = TranslatorRegistry.getTranslators();
addPath(getSearchConfigDir());
addPath(getDefaultConfigFilePath());
parseFiles();
}
/**
* Construct from a Reader.
* @param rdr Configuration data in Reader format.
* @throws ConfigException thrown if a problem reading the configuration
* occurs.
*/
public Configuration(Reader rdr) throws ConfigException {
translators = TranslatorRegistry.getTranslators();
ReaderWrapper rdrwrap = new ReaderWrapper(rdr);
Properties props = new Properties();
try {
parseStream(props, "search", rdrwrap);
}
catch (IOException ioe) {
throw new ConfigException(ioe.getMessage(), ioe);
}
}
/**
* Add a path to the config object for parsing
* @param path The path to add
*/
public void addPath(String path) {
addFiles(path);
}
/**
* static method to get the singleton Config option
*
* @return the config option
*/
// public static synchronized Config get() {
// if (singletonConfig == null) {
// singletonConfig = new Config();
// }
// return singletonConfig;
// }
private static String getRHNConfigDir() {
String confDir = System.getProperty(RHN_CONF_DIR_PROPERTY);
if (StringUtils.isBlank(confDir)) {
confDir = DEFAULT_CONF_DIR;
}
return confDir;
}
private static String getSearchConfigDir() {
String confDir = System.getProperty(SEARCH_CONF_DIR_PROPERTY);
if (StringUtils.isBlank(confDir)) {
confDir = DEFAULT_DEFAULT_CONF_DIR;
}
return confDir;
}
/**
* Get the path to the rhn.conf file we use.
*
* @return String path.
*/
public static String getDefaultConfigFilePath() {
return getRHNConfigDir() + "/rhn.conf";
}
/**
* Get the configuration entry for the given string name. If the value
* is null, then return the given defValue. defValue can be null as well.
* @param name name of property
* @param defValue default value for property if it is null.
* @return the value of the property with the given name, or defValue.
*/
public String getString(String name, String defValue) {
String ret = getString(name);
if (ret == null) {
if (logger.isDebugEnabled()) {
logger.debug("getString() - returning default value");
}
ret = defValue;
}
return ret;
}
/**
* Returns a map of the values with the given name. The value must be
* in the format of key:value otherwise, an empty map is returned.
* @param name name of property
* @return map of the values of the property matching k:v.
*/
public Map<String, String> getMap(String name) {
String value = getString(name);
Map<String,String> retval = null;
if (value != null) {
retval = new HashMap<String, String>();
String[] pairs = value.split(",");
for (int x = 0; x < pairs.length; x++) {
String[] nv = pairs[x].split(":");
if (nv.length != 2) {
continue;
}
retval.put(nv[0].trim(), nv[1].trim());
}
}
return retval;
}
/**
* get the config entry for string s
*
* @param value string to get the value of
* @return the value
*/
public String getString(String value) {
if (logger.isDebugEnabled()) {
logger.debug("getString() - getString() called with: " + value);
}
if (value == null) {
return null;
}
int lastDot = value.lastIndexOf(".");
String ns = "";
String property = value;
if (lastDot > 0) {
property = value.substring(lastDot + 1);
ns = value.substring(0, lastDot);
}
if (logger.isDebugEnabled()) {
logger.debug("getString() - getString() -> Getting property: " +
property);
}
String result = configValues.getProperty(property);
if (logger.isDebugEnabled()) {
logger.debug("getString() - getString() -> result: " + result);
}
if (result == null) {
if (!"".equals(ns)) {
result = configValues.getProperty(ns + "." + property);
}
else {
for (int i = 0; i < prefixOrder.length; i++) {
result = configValues.getProperty(prefixOrder[i] + "." + property);
if (result != null) {
break;
}
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("getString() - getString() -> returning: " + result);
}
if (result == null || result.equals("")) {
return null;
}
return StringUtils.trim(result);
}
/**
* get the config entry for string s
*
* @param s string to get the value of
* @return the value
*/
public int getInt(String s) {
return getInt(s, 0);
}
/**
* get the config entry for string s, if no value is found
* return the defaultValue specified.
*
* @param s string to get the value of
* @param defaultValue Default value if entry is not found.
* @return the value
*/
public int getInt(String s, int defaultValue) {
Integer val = getInteger(s);
if (val == null) {
return defaultValue;
}
return val.intValue();
}
/**
* get the config entry for string s
*
* @param s string to get the value of
* @return the value
*/
public double getDouble(String s) {
return getDouble(s, 0.0);
}
/**
* get the config entry for string s, if no value is found
* return the defaultValue specified.
*
* @param s string to get the value of
* @param defaultValue Default value if entry is not found.
* @return the value
*/
public double getDouble(String s, double defaultValue) {
String val = getString(s);
if (val == null) {
return defaultValue;
}
return new Double(val).doubleValue();
}
/**
* get the config entry for string s
*
* @param s string to get the value of
* @return the value
*/
public Integer getInteger(String s) {
String val = getString(s);
if (val == null) {
return null;
}
return new Integer(val);
}
/**
* Parses a comma-delimited list of values as a java.util.List
* @param name config entry name
* @return instance of java.util.List populated with config values
*/
public List<String> getList(String name) {
List<String> retval = new LinkedList<String>();
String[] vals = getStringArray(name);
if (vals != null) {
retval.addAll(Arrays.asList(vals));
}
return retval;
}
/**
* get the config entry for string s
*
* @param s string to get the value of
* @return the value
*/
public String[] getStringArray(String s) {
if (s == null) {
return null;
}
String value = getString(s);
if (value == null) {
return null;
}
return value.split(",");
}
/**
* get the config entry for string name
*
* @param name string to set the value of
* @param value new value
* @return the previous value of the property
*/
public String setString(String name, String value) {
return (String) configValues.setProperty(name, value);
}
/**
* get the config entry for string s
*
* @param s string to get the value of
* @return the value
*/
public boolean getBoolean(String s) {
String value = getString(s);
if (logger.isDebugEnabled()) {
logger.debug("getBoolean() - " + s + " is : " + value);
}
if (value == null) {
return false;
}
//need to check the possible true values
// tried to use BooleanUtils, but that didn't
// get the job done for an integer as a String.
for (int i = 0; i < TRUE_VALUES.length; i++) {
if (TRUE_VALUES[i].equalsIgnoreCase(value)) {
if (logger.isDebugEnabled()) {
logger.debug("getBoolean() - Returning true: " + value);
}
return true;
}
}
return false;
}
/**
* set the config entry for string name
* @param s string to set the value of
* @param b new value
*/
public void setBoolean(String s, String b) {
// need to check the possible true values
// tried to use BooleanUtils, but that didn't
// get the job done for an integer as a String.
for (int i = 0; i < TRUE_VALUES.length; i++) {
if (TRUE_VALUES[i].equalsIgnoreCase(b)) {
configValues.setProperty(s, "1");
// get out we're done here
return;
}
}
configValues.setProperty(s, "0");
}
private void addFiles(String path) {
File f = new File(path);
if (f.isDirectory()) {
// bugzilla: 154517; only add items that end in .conf
File[] files = f.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].getName().endsWith((".conf"))) {
fileList.add(files[i]);
}
}
}
else {
fileList.add(f);
}
}
private String makeNamespace(File f) {
String ns = f.getName();
// This is really hokey, but it works. Basically, rhn.conf doesn't
// match the standard rhn_foo.conf convention. So, to create the
// namespace, we first special case rhn.*
if (ns.startsWith("rhn.")) {
return "";
}
ns = ns.replaceFirst("rhn_", "");
ns = ns.substring(0, ns.lastIndexOf('.'));
ns = ns.replaceAll("_", ".");
return ns;
}
private void parseStream(Properties props, String namespace, InputStream is)
throws IOException {
props.load(is);
// loop through all of the config values in the properties file
// making sure the prefix is there.
Properties newProps = new Properties();
for (Iterator j = props.keySet().iterator(); j.hasNext();) {
String key = (String) j.next();
String newKey = key;
if (!key.startsWith(namespace)) {
newKey = namespace + "." + key;
}
logger.debug("Adding: " + newKey + ": " + props.getProperty(key));
// translate the original key
newKey = translateKey(key);
newProps.put(newKey, props.getProperty(key));
}
configValues.putAll(newProps);
}
/**
* Parse all of the added files.
*/
public void parseFiles() {
for (Iterator i = fileList.iterator(); i.hasNext();) {
File curr = (File) i.next();
parseFile(curr);
}
}
private void parseFile(File file) {
Properties props = new Properties();
String ns = makeNamespace(file);
logger.debug("Adding namespace: " + ns + " for file: " +
file.getAbsolutePath());
try {
parseStream(props, ns, new FileInputStream(file));
}
catch (IOException e) {
logger.error("Could not parse file" + file, e);
}
}
/**
* Returns a subset of the properties for the given namespace. This is
* not a particularly fast method and should be used only at startup or
* some other discreet time. Repeated calls to this method are guaranteed
* to be slow.
* @param namespace Namespace of properties to be returned.
* @return subset of the properties that begin with the given namespace.
*/
public Properties getNamespaceProperties(String namespace) {
Properties prop = new Properties();
for (Iterator i = configValues.keySet().iterator(); i.hasNext();) {
String key = (String) i.next();
if (key.startsWith(namespace)) {
if (logger.isDebugEnabled()) {
logger.debug("Looking for key: [" + key + "]");
}
prop.put(key, configValues.getProperty(key));
}
}
return prop;
}
private String translateKey(String key) {
for (Iterator itr = translators.iterator(); itr.hasNext();) {
KeyTranslator trans = (KeyTranslator) itr.next();
if (trans.shouldTranslate(key)) {
key = trans.translateKey(key);
}
}
return key;
}
private static class ReaderWrapper extends InputStream {
private Reader rdr;
public ReaderWrapper(Reader reader) {
rdr = reader;
}
/**
* {@inheritDoc}
*/
@Override
public int read() throws IOException {
return rdr.read();
}
@Override
public void close() throws IOException {
super.close();
rdr.close();
}
}
}