Java tutorial
/************************************************************************* * * * 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.Map.Entry; 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 18281 2013-12-13 09:27:09Z anatom $ */ public final class ConfigurationHolder { private static final Logger log = Logger.getLogger(ConfigurationHolder.class); private static volatile CompositeConfiguration defaultValues; private static 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; } /** * 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()); 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()); } } /** * 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); config.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(); @SuppressWarnings("unchecked") final Iterator<String> i = instance().getKeys(); while (i.hasNext()) { final String key = (String) 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(); } /** * In a list of properties named "something.NAME.xxx.yyy", returns all * unique names (the NAME part only) in sorted order. */ public static List<String> getPrefixedPropertyNames(String prefix) { prefix = prefix + "."; Set<String> algs = new HashSet<String>(); Properties props = ConfigurationHolder.getAsProperties(); for (Entry<Object, Object> entry : props.entrySet()) { final String key = (String) entry.getKey(); if (key.startsWith(prefix)) { int dot = key.indexOf(".", prefix.length()); algs.add(key.substring(prefix.length(), dot)); } } ArrayList<String> list = new ArrayList<String>(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. for (Object key : properties.keySet()) { final String value = (String) properties.get((String) key); config.setProperty((String) key, value); } 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 String key, final String value) { backupConfiguration(); // Only takes a backup if necessary. config.setProperty(key, value); return true; } }