All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cesecore.config.ConfigurationHolder Maven / Gradle / Ivy

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/

package org.cesecore.config;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.SystemConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.log4j.Logger;

/**
 * This is a singleton. Used to configure common-configuration with our sources.
 *
 * Use like this: String value = ConfigurationHolder.getString("my.conf.property.key"); or String value =
 * ConfigurationHolder.getString("my.conf.property.key", "default value"); or String value =
 * ConfigurationHolder.getExpandedString("my.conf.property.key", "default value"); to be able to parse values containing ${property}
 *
 * See in-line comments below for the sources added to the configuration.
 *
 * @version $Id: ConfigurationHolder.java 26955 2017-10-31 13:41:00Z samuellb $
 */
public final class ConfigurationHolder {

    private static final Logger log = Logger.getLogger(ConfigurationHolder.class);

    private static volatile CompositeConfiguration defaultValues;

    private static volatile CompositeConfiguration config = null;
    private static CompositeConfiguration configBackup = null;

    /** cesecore.properties must be first in this file, because CONFIGALLOWEXTERNAL is defined in there.
     * NOTE: diff between EJBCA and CESeCore*/
    private static final String[] CONFIG_FILES = { "cesecore.properties", "extendedkeyusage.properties",
            "cache.properties", "database.properties", "databaseprotection.properties", "backup.properties", "va.properties", "ocsp.properties"};

    /** Configuration property that enables dynamic reading of properties from the file system. This is not allowed by default for security reasons. */
    private static final String CONFIGALLOWEXTERNAL = "allow.external-dynamic.configuration";

    private static final String DEFAULT_CONFIG_FILE = "/defaultvalues.properties";

    /** This is a singleton so it's not allowed to create an instance explicitly */
    private ConfigurationHolder() {
    }

    public static synchronized Configuration instance() {
        if (config == null) {
            // Read in default values
            defaultValues = new CompositeConfiguration();
            final URL defaultConfigUrl = ConfigurationHolder.class.getResource(DEFAULT_CONFIG_FILE);
            try {
                defaultValues.addConfiguration(new PropertiesConfiguration(defaultConfigUrl));
            } catch (ConfigurationException e) {
                log.error("Error encountered when loading default properties. Could not load configuration from " + defaultConfigUrl, e);
            }

            // read cesecore.properties, from config file built into jar, and see if we allow configuration by external files
            boolean allowexternal = false;
            try {
                final URL url = ConfigurationHolder.class.getResource("/conf/"+CONFIG_FILES[0]);
                if (url != null) {
                    final PropertiesConfiguration pc = new PropertiesConfiguration(url);
                    allowexternal = "true".equalsIgnoreCase(pc.getString(CONFIGALLOWEXTERNAL, "false"));
                    log.info("Allow external re-configuration: " + allowexternal);
                }
            } catch (ConfigurationException e) {
                log.error("Error intializing configuration: ", e);
            }
            config = new CompositeConfiguration();

            // Only add these config sources if we allow external configuration
            if (allowexternal) {
                // Override with system properties, this is prio 1 if it exists (java -Dscep.test=foo)
                config.addConfiguration(new SystemConfiguration());
                log.info("Added system properties to configuration source (java -Dfoo.prop=bar).");

                // Override with file in "application server home directory"/conf, this is prio 2
                for (int i = 0; i < CONFIG_FILES.length; i++) {
                    File f = null;
                    try {
                        f = new File("conf" + File.separator + CONFIG_FILES[i]);
                        final PropertiesConfiguration pc = new PropertiesConfiguration(f);
                        pc.setReloadingStrategy(new FileChangedReloadingStrategy());
                        config.addConfiguration(pc);
                        log.info("Added file to configuration source: " + f.getAbsolutePath());
                    } catch (ConfigurationException e) {
                        log.error("Failed to load configuration from file " + f.getAbsolutePath());
                    }
                }
                // Override with file in "/etc/cesecore/conf/, this is prio 3
                for (int i = 0; i < CONFIG_FILES.length; i++) {
                    File f = null;
                    try {
                        f = new File("/etc/cesecore/conf/" + CONFIG_FILES[i]);
                        final PropertiesConfiguration pc = new PropertiesConfiguration(f);
                        pc.setReloadingStrategy(new FileChangedReloadingStrategy());
                        config.addConfiguration(pc);
                        log.info("Added file to configuration source: " + f.getAbsolutePath());
                    } catch (ConfigurationException e) {
                        log.error("Failed to load configuration from file " + f.getAbsolutePath());
                    }
                }
            } // if (allowexternal)

            // Default values build into jar file, this is last prio used if no of the other sources override this
            for (int i = 0; i < CONFIG_FILES.length; i++) {
                addConfigurationResource("/conf/"+CONFIG_FILES[i]);
            }
            // Load internal.properties only from built in configuration file
            try {
                final URL url = ConfigurationHolder.class.getResource("/internal.properties");
                if (url != null) {
                    final PropertiesConfiguration pc = new PropertiesConfiguration(url);
                    config.addConfiguration(pc);
                    log.debug("Added url to configuration source: " + url);
                }
            } catch (ConfigurationException e) {
                log.error("Failed to load configuration from resource internal.properties", e);
            }
        }
        return config;
    }
    
    private static synchronized void addConfiguration(final PropertiesConfiguration pc) {
        // The try/catch is needed with commons-configuration 1.10 (but not with 1.06)
//        try {
            final CompositeConfiguration cfgClone = (CompositeConfiguration) config.clone();
            cfgClone.addConfiguration(pc);
            config = cfgClone; // atomic replacement, since we don't want to require all the get*() methods to be synchronized
//        } catch (ConfigurationRuntimeException e) {
//            // Appears to happen due to some bug in MapConfiguration (on certain systems only)
//            if (log.isDebugEnabled()) {
//                log.debug("Configuration class " + config.getClass().getName() + " is not cloneable. Falling back to not fully thread safe code.", e);
//            }
//            config.addConfiguration(pc);
//        }
    }

    /**
     * Method used primarily for JUnit testing, where we can add a new properties file (in tmp directory) to the configuration.
     *
     * @param filename the full path to the properties file used for configuration.
     */
    public static void addConfigurationFile(final String filename) {
        // Make sure the basic initialization has been done
        instance();
        File f = null;
        try {
            f = new File(filename);
            final PropertiesConfiguration pc = new PropertiesConfiguration(f);
            pc.setReloadingStrategy(new FileChangedReloadingStrategy());
            addConfiguration(pc);
            log.info("Added file to configuration source: " + f.getAbsolutePath());
        } catch (ConfigurationException e) {
            log.error("Failed to load configuration from file " + f.getAbsolutePath());
        }
    }

    /**
     * Add built in config file
     */
    public static void addConfigurationResource(final String resourcename) {
        // Make sure the basic initialization has been done
        instance();
        if (log.isDebugEnabled()) {
            log.debug("Add resource to configuration: " + resourcename);
        }
        try {
            final URL url = ConfigurationHolder.class.getResource(resourcename);
            if (url != null) {
                final PropertiesConfiguration pc = new PropertiesConfiguration(url);
                addConfiguration(pc);
                if (log.isDebugEnabled()) {
                    log.debug("Added url to configuration source: " + url);
                }
            }
        } catch (ConfigurationException e) {
            log.error("Failed to load configuration from resource " + resourcename, e);
        }
    }

    /**
     * @return the configuration as a regular Properties object
     */
    public static Properties getAsProperties() {
        final Properties properties = new Properties();
        final Iterator i = instance().getKeys();
        while (i.hasNext()) {
            final String key = i.next();
            properties.setProperty(key, instance().getString(key));
        }
        return properties;
    }

    /**
     * @param property the property to look for
     * @return String configured for property, or the default value defined in defaultvalues.properties, or null if no such value exists
     */
    public static String getString(final String property) {
        // Commons configuration interprets ','-separated values as an array of Strings, but we need the whole String for example SubjectDNs.
        final StringBuilder str = new StringBuilder();
        String rets[] = instance().getStringArray(property);
        if (rets.length == 0) {
            rets = defaultValues.getStringArray(property);
        }
        for (int i = 0; i < rets.length; i++) {
            if (i != 0) {
                str.append(',');
            }
            str.append(rets[i]);
        }
        final String ret;
        if (str.length() != 0) {
            ret = str.toString();
        } else {
            ret = defaultValues.getString(property);
        }
        return ret;
    }

    public static String getDefaultValue(final String property) {
        instance();
        return defaultValues.getString(property);
    }

    public static String[] getDefaultValueArray(final String property) {
        instance();
        return defaultValues.getStringArray(property);
    }

    /**
     * Return a the expanded version of a property. E.g. property1=foo property2=${property1}bar would return "foobar" for property2
     *
     * @param property the property to look for
     * @return String configured for property, or the default value defined in defaultvalues.properties, or null if no such value exists
     */
    public static String getExpandedString(final String property) {
        String ret = getString(property);
        if (ret != null) {
            while (ret.indexOf("${") != -1) {
                ret = interpolate(ret);
            }
        }
        return ret;
    }

    private static String interpolate(final String orderString) {
        final Pattern PATTERN = Pattern.compile("\\$\\{(.+?)\\}");
        final Matcher m = PATTERN.matcher(orderString);
        final StringBuffer sb = new StringBuffer(orderString.length());
        m.reset();
        while (m.find()) {
            // when the pattern is ${identifier}, group 0 is 'identifier'
            final String key = m.group(1);
            final String value = getExpandedString(key);

            // if the pattern does exists, replace it by its value
            // otherwise keep the pattern ( it is group(0) )
            if (value != null) {
                m.appendReplacement(sb, value);
            } else {
                // I'm doing this to avoid the backreference problem as there will be a $
                // if I replace directly with the group 0 (which is also a pattern)
                m.appendReplacement(sb, "");
                final String unknown = m.group(0);
                sb.append(unknown);
            }
        }
        m.appendTail(sb);
        return sb.toString();
    }

    /**
     * Given the prefix "something", in a list of properties named "something.NAME.xxx.yyy", returns all
     * unique names (the NAME part only) in sorted order.
     */
    public static List getPrefixedPropertyNames(String prefix) {
        Set algs = new HashSet();
        // Just get the keys from configuration that starts with prefix, we assume below that it has a . following the prefix
        Iterator iterator = config.getKeys(prefix);
        while (iterator.hasNext()) {
            final String key = iterator.next();
            final int dot = key.indexOf(".", prefix.length()+1); // +1 to skip the . directly following prefix
            algs.add(key.substring(prefix.length()+1, dot));
        }
        ArrayList list = new ArrayList(algs);
        Collections.sort(list);
        return list;
    }

    /**
     * Backups the original configuration in a non thread safe way. This backup stores the current configuration in a variable, this makes it possible
     * to test a configuration change and revert to the original configuration (cancel change). It is used in conjunction with updateConfiguration and
     * restore configuration.
     *
     * Normally used by functional (system) tests, but can also be used to "try" configuraiton settings in a live system.
     */
    public static boolean backupConfiguration() {
        if (configBackup != null) {
            return false;
        }
        instance();
        configBackup = (CompositeConfiguration) config.clone();
        return true;
    }

    /**
     * Restores the original configuration in a non thread safe way. It restores a previously backed up configuration. The backup stores the current
     * configuration in a variable, this makes it possible to test a configuration change and revert to the original configuration (cancel change). It
     * is used in conjunction with updateConfiguration and backup configuration.
     *
     * Normally used by functional (system) tests, but can also be used to "try" configuration settings in a live system.
     */
    public static boolean restoreConfiguration() {
        if (configBackup == null) {
            return false;
        }
        config = configBackup;
        configBackup = null;
        return true;
    }

    /**
     * Takes a backup of the active configuration if necessary and updates the active configuration. Does not persist the configuration change to disk
     * or database, so it is volatile during the running of the application. Persisting the configuration must be handles outside of this method.
     *
     * Normally used by functional (system) tests, but can also be used to "try" configuration settings in a live system.
     */
    public static boolean updateConfiguration(final Properties properties) {
        backupConfiguration(); // Only takes a backup if necessary.
        return updateConfigurationWithoutBackup(properties);
    }

    /**
     * Takes a backup of the active configuration if necessary and updates the active configuration. Does not persist the configuration change to disk
     * or database, so it is volatile during the running of the application. Persisting the configuration must be handles outside of this method.
     *
     * Normally used by functional (system) tests, but can also be used to "try" configuration settings in a live system.
     */
    public static boolean updateConfiguration(final String key, final String value) {
        backupConfiguration(); // Only takes a backup if necessary.
        return updateConfigurationWithoutBackup(key, value);
    }

    /**
     * Updates the active configuration. Does not persist the configuration change to disk or database, so it is volatile during the running of the application.
     * Persisting the configuration must be handles outside of this method.
     */
    public static boolean updateConfigurationWithoutBackup(final Properties properties) {
        for (Object key : properties.keySet()) {

            final String value = (String) properties.get(key);
            config.setProperty((String) key, value);
        }
        return true;
    }

    /**
     * Updates the active configuration. Does not persist the configuration change to disk or database, so it is volatile during the running of the application.
     * Persisting the configuration must be handles outside of this method.
     */
    public static boolean updateConfigurationWithoutBackup(final String key, final String value) {
        config.setProperty(key, value);
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy