Java tutorial
/* $HeadURL:: $ * $Id$ * * Copyright (c) 2006-2010 by Public Library of Science * http://plos.org * http://ambraproject.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ambraproject.configuration; import org.apache.commons.configuration.AbstractConfiguration; import org.apache.commons.configuration.CombinedConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.ConfigurationUtils; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.configuration.SystemConfiguration; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.configuration.tree.OverrideCombiner; import org.apache.commons.configuration.tree.UnionCombiner; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.Enumeration; /** * A singleton that manages the load/unload/re-load of Configuration.<p> * * Configuration consists of a layered set of configuration files where configuration * in a higher layer overrides those of the lower layers. Starting from the lowest layer, * configuration consists of: * <ul> * <li><var>/global-defaults.xml</var> - A resource in this library * <li><var>/defaults.xml</var> - A resource or resources in libraries and webapps using this lib * <li><var>ambra.configuration.overrides</var> - If set, this defines a named resource or URL * of a resource that is added to the configuration tree - usually supplementing * and overriding settings in <var>/global-defaults.xml</var> and <var>/defaults.xml</var>. * <li><var>file:/etc/ambra/ambra.xml</var> (or <var>ambra.configuration</var>) - A set of user * overrides in <var>/etc</var>. The name of this file can be changed for webapps that use * WebAppInitializer by changing web.xml or by setting the ambra.configuraiton system * property. * <li>System properties * </ul> * * @author Pradeep Krishnan * @author Eric Brown */ public class ConfigurationStore { private static final Log log = LogFactory.getLog(ConfigurationStore.class); private static final ConfigurationStore instance = new ConfigurationStore(); private CombinedConfiguration root = null; /** * A property used to define the location of the master set of configuration overrides. * This is usually a xml or properties file in /etc somewhere. Note that this must be * a URL. (For example: file:///etc/ambra/ambra.xml.) */ public static final String CONFIG_URL = "ambra.configuration"; /** * A property used to define overrides. This is primarily to support something like * a development mode. If a valid URL, the resource is found from the URL. If not a * URL, it is treated as the name of a resource. */ public static final String OVERRIDES_URL = "ambra.configuration.overrides"; /** * Default configuration overrides in /etc */ public static final String DEFAULT_CONFIG_URL = "file:///etc/ambra/ambra.xml"; /** * <p>Name of resource(s) that contain defaults in a given journal</p> * * <p>There is one per journal.</p> */ public static final String JOURNAL_DIRECTORY = "/configuration/journal.xml"; public static final String DEFAULTS_RESOURCE = "ambra/configuration/defaults.xml"; /** * The name of the global defaults that exist in this library.<p> * * It is assumed there is only one of these in the classpath. If somebody defines * a second copy of this, the results are undefined. (TODO: Detect this.) */ public static final String GLOBAL_DEFAULTS_RESOURCE = "/ambra/configuration/global-defaults.xml"; /** * The system variable used by Hibernates ID generator to use a prefix for unique identifiers * This value is pulled from the ambra.xml 'config.ambra.aliases.id' node. */ public static final String SYSTEM_OBJECT_ID_PREFIX = "SYSTEM_OBJECT_ID_PREFIX"; /** * Advanced usage logging */ public static final String ADVANCED_USAGE_LOGGING = "ambra.advancedUsageLogging"; /** * Location of journal templates */ public static final String JOURNAL_TEMPLATE_DIR = "ambra.virtualJournals.templateDir"; private static final String JOURNALS = "ambra.virtualJournals.journals"; /** * Create the singleton instance. */ private ConfigurationStore() { } /** * Gets the singleton instance. * * @return Returns the only instance. */ public static ConfigurationStore getInstance() { return instance; } /** * Gets the current configuration root. * * @return Returns the currently loaded configuration root * * @throws RuntimeException if the configuration factory is not initialized */ public Configuration getConfiguration() { if (root != null) return root; throw new RuntimeException("ERROR: Configuration not loaded or initialized."); } /** * Overrides all existing configuration with the given configuration object * (useful for JUnit testing!) * @param newConfig the new configuration to test */ public void setConfiguration(CombinedConfiguration newConfig) { root = newConfig; } /** * Load/Reload the configuration from the factory config url. * * @param configURL URL to the config file for ConfigurationFactory * @throws ConfigurationException when the config factory configuration has an error */ public void loadConfiguration(URL configURL) throws ConfigurationException { root = new CombinedConfiguration(new OverrideCombiner()); // System properties override everything root.addConfiguration(new SystemConfiguration()); // Load from ambra.configuration -- /etc/... (optional) if (configURL != null) { try { root.addConfiguration(getConfigurationFromUrl(configURL)); log.info("Added URL '" + configURL + "'"); } catch (ConfigurationException ce) { if (!(ce.getCause() instanceof FileNotFoundException)) throw ce; log.info("Unable to open '" + configURL + "'"); } } // Add ambra.configuration.overrides (if defined) String overrides = System.getProperty(OVERRIDES_URL); if (overrides != null) { try { root.addConfiguration(getConfigurationFromUrl(new URL(overrides))); log.info("Added override URL '" + overrides + "'"); } catch (MalformedURLException mue) { // Must not be a URL, so it must be a resource addResources(root, overrides); } } CombinedConfiguration defaults = new CombinedConfiguration(new UnionCombiner()); // Add defaults.xml from classpath addResources(defaults, DEFAULTS_RESOURCE); // Add journal.xml from journals/journal-name/configuration/journal.xml addJournalResources(root, defaults, JOURNAL_DIRECTORY); root.addConfiguration(defaults); // Add global-defaults.xml (presumably found in this jar) addResources(root, GLOBAL_DEFAULTS_RESOURCE); if (log.isDebugEnabled()) log.debug("Configuration dump: " + System.getProperty("line.separator") + ConfigurationUtils.toString(root)); /** * This prefix is needed by the AmbraIdGenerator to create prefixes for object IDs. * Because of the way the AmbraIdGenerator class is created by hibernate, passing in values * is very difficult. If a better method is discovered... by all means use that. Until that time * I've created a system level property to store this prefix. */ String objectIDPrefix = root.getString("ambra.platform.guid-prefix"); if (objectIDPrefix == null) { throw new RuntimeException( "ambra.platform.guid-prefix node is not found in the defined configuration file."); } System.setProperty(SYSTEM_OBJECT_ID_PREFIX, objectIDPrefix); } /** * Use the default commons configuration specified by this library. * * @throws ConfigurationException when the configuration can't be found. */ public void loadDefaultConfiguration() throws ConfigurationException { // Allow JVM level property to override everything else String name = System.getProperty(CONFIG_URL); if (name == null) name = DEFAULT_CONFIG_URL; try { loadConfiguration(new URL(name)); } catch (MalformedURLException e) { throw new ConfigurationException( "Invalid value of '" + name + "' for '" + CONFIG_URL + "'. Must be a valid URL."); } } /** * Unload the current configuration. */ public void unloadConfiguration() { root = null; } /** * Given a URL, determine whether it represents properties or xml and load it as a * commons-config Configuration instance. */ private static AbstractConfiguration getConfigurationFromUrl(URL url) throws ConfigurationException { if (url.getFile().endsWith("properties")) return new PropertiesConfiguration(url); else return new XMLConfiguration(url); } /** * Iterate over all the resources of the given name and add them to our root * configuration. * @param root the root configuration to add to * @param resource the resource to add * @throws ConfigurationException on an error in adding the new config */ public static void addResources(CombinedConfiguration root, String resource) throws ConfigurationException { Class<?> klass = ConfigurationStore.class; if (resource.startsWith("/")) { root.addConfiguration(getConfigurationFromUrl(klass.getResource(resource))); log.info("Added resource '" + resource + "' to configuration"); } else { try { Enumeration<URL> rs = klass.getClassLoader().getResources(resource); while (rs.hasMoreElements()) { URL resourceUrl = rs.nextElement(); root.addConfiguration(getConfigurationFromUrl(resourceUrl)); log.info("Added resource '" + resourceUrl + "' from path '" + resource + "' to configuration"); } } catch (IOException ioe) { throw new Error("Unexpected error loading resources", ioe); } } } public static void addJournalResources(CombinedConfiguration root, CombinedConfiguration defaults, String path) throws ConfigurationException { Collection<String> journals = root.getList(JOURNALS); String journalTemplatePath = root.getString(ConfigurationStore.JOURNAL_TEMPLATE_DIR, "/"); for (String journal : journals) { String resourcePath = journalTemplatePath + (journalTemplatePath.endsWith("/") ? "journals/" : "/journals/") + journal + path; File defaultsXmlFile = new File(resourcePath); if (defaultsXmlFile.isFile() && defaultsXmlFile.canRead()) { defaults.addConfiguration(new XMLConfiguration(defaultsXmlFile)); log.info("Added resource '" + resourcePath + "' to configuration"); } } } }