/** * Copyright (c) 2009--2014 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.rhn.common.conf; 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.InputStreamReader; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; 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/rhn.conf * setting defaults that can be overridden by /etc/rhn/rhn.conf. * * @version $Rev$ */ public class Config { private static Logger logger = Logger.getLogger(Config.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 configuration directory. * If the property is not set, config files are read * from {@link #DEFAULT_CONF_DIR} */ private static final String CONF_DIR_PROPERTY = "rhn.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 final String[] prefixOrder = new String[] {"web", "server"}; private static Config singletonConfig = null; /** hash of configuration properties */ private final Properties configValues = new Properties(); /** set of configuration file names */ private final 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. * sometioes 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 specilize ConfigException and catch * those */ public Config() throws ConfigException { addPath(DEFAULT_DEFAULT_CONF_DIR); addPath(getDefaultConfigFilePath()); parseFiles(); } /** * Add a path to the config object for parsing * @param path The path to add */ public void addPath(String path) { getFiles(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 getDefaultConfigDir() { String confDir = System.getProperty(CONF_DIR_PROPERTY); if (StringUtils.isBlank(confDir)) { confDir = DEFAULT_CONF_DIR; } return confDir; } /** * Get the path to the rhn.conf file we use. * * @return String path. */ public static String getDefaultConfigFilePath() { return getDefaultConfigDir() + "/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; } /** * @param name Key to check for * @return true if the config contains key */ public boolean containsKey(String name) { return configValues.containsKey(name); } /** * 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 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); } /** * remove the config entry for key * @param name key to remove */ public void remove(String name) { configValues.remove(name); } /** * 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 getFiles(String path) { File f = new File(path); if (f.isDirectory()) { // bugzilla: 154517; only add items that end in .conf File[] files = f.listFiles(); if (files == null) { logger.error("Unable to list files in path : " + path); return; } 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_", ""); int lastDotindex = ns.lastIndexOf('.'); if (lastDotindex != -1) { ns = ns.substring(0, ns.lastIndexOf('.')); } ns = ns.replaceAll("_", "."); return ns; } /** * Parse all of the added files. */ public void parseFiles() { for (Iterator<File> i = fileList.iterator(); i.hasNext();) { File curr = i.next(); Properties props = new Properties(); try { props.load(new InputStreamReader(new FileInputStream(curr), "UTF-8")); } catch (IOException e) { logger.error("Could not parse file " + curr, e); } String ns = makeNamespace(curr); logger.debug("Adding namespace: " + ns + " for file: " + curr.getAbsolutePath()); // 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(ns)) { newKey = ns + "." + key; } logger.debug("Adding: " + newKey + ": " + props.getProperty(key)); newProps.put(newKey, props.getProperty(key)); } configValues.putAll(newProps); } } /** * 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; } }