com.netflix.config.ConfigurationManager.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.config.ConfigurationManager.java

Source

/*
 *
 *  Copyright 2012 Netflix, Inc.
 *
 *     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 com.netflix.config;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.SystemConfiguration;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.config.jmx.ConfigJMXManager;
import com.netflix.config.jmx.ConfigMBean;
import com.netflix.config.util.ConfigurationUtils;

/**
 * The configuration manager is a central place where it manages the system wide Configuration and
 * deployment context.
 * <p>
 * During initialization, this class will check system property "archaius.default.configuration.class"
 * and "archaius.default.configuration.factory". If the former is set, it will use the class name to instantiate 
 * it using its default no-arg constructor. If the later is set, it will call its static method getInstance().
 * In both cases, the returned Configuration object will be set as the system wide configuration.
 * 
 * @author awang
 *
 */
public class ConfigurationManager {

    static volatile AbstractConfiguration instance = null;
    static volatile boolean customConfigurationInstalled = false;
    private static volatile ConfigMBean configMBean = null;
    private static final Logger logger = LoggerFactory.getLogger(ConfigurationManager.class);
    static volatile DeploymentContext context = null;

    public static final String PROP_NEXT_LOAD = "@next";

    private static Set<String> loadedPropertiesURLs = new CopyOnWriteArraySet<String>();

    static {
        try {
            String className = System.getProperty("archaius.default.configuration.class");
            if (className != null) {
                instance = (AbstractConfiguration) Class.forName(className).newInstance();
                customConfigurationInstalled = true;
            } else {
                String factoryName = System.getProperty("archaius.default.configuration.factory");
                if (factoryName != null) {
                    Method m = Class.forName(factoryName).getDeclaredMethod("getInstance", new Class[] {});
                    m.setAccessible(true);
                    instance = (AbstractConfiguration) m.invoke(null, new Object[] {});
                    customConfigurationInstalled = true;
                }
            }
            String contextClassName = System.getProperty("archaius.default.deploymentContext.class");
            if (contextClassName != null) {
                setDeploymentContext((DeploymentContext) Class.forName(contextClassName).newInstance());
            } else {
                String factoryName = System.getProperty("archaius.default.deploymentContext.factory");
                if (factoryName != null) {
                    Method m = Class.forName(factoryName).getDeclaredMethod("getInstance", new Class[] {});
                    m.setAccessible(true);
                    setDeploymentContext((DeploymentContext) m.invoke(null, new Object[] {}));
                } else {
                    setDeploymentContext(new ConfigurationBasedDeploymentContext());
                }
            }

        } catch (Exception e) {
            throw new RuntimeException("Error initializing configuration", e);
        }
    }

    public static Set<String> getLoadedPropertiesURLs() {
        return loadedPropertiesURLs;
    }

    /**
     * Install the system wide configuration with the ConfigurationManager. This will also install 
     * the configuration with the {@link DynamicPropertyFactory} by calling {@link DynamicPropertyFactory#initWithConfigurationSource(AbstractConfiguration)}.
     * This call can be made only once, otherwise IllegalStateException will be thrown.
     */
    public static synchronized void install(AbstractConfiguration config) throws IllegalStateException {
        if (!customConfigurationInstalled) {
            setDirect(config);
            if (DynamicPropertyFactory.getBackingConfigurationSource() != config) {
                DynamicPropertyFactory.initWithConfigurationSource(config);
            }
        } else {
            throw new IllegalStateException("A non-default configuration is already installed");
        }
    }

    public static synchronized boolean isConfigurationInstalled() {
        return customConfigurationInstalled;
    }

    private static AbstractConfiguration createDefaultConfigInstance() {
        ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
        if (!Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_SYS_CONFIG)) {
            try {
                DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
                config.addConfiguration(defaultURLConfig, DynamicPropertyFactory.URL_CONFIG_NAME);
            } catch (Throwable e) {
                logger.warn("Failed to create default dynamic configuration", e);
            }
            SystemConfiguration sysConfig = new SystemConfiguration();
            config.addConfiguration(sysConfig, DynamicPropertyFactory.SYS_CONFIG_NAME);
            int index = config.getIndexOfConfiguration(sysConfig);
            config.setContainerConfigurationIndex(index);
        }
        return config;
    }

    private static AbstractConfiguration getConfigInstance(boolean defaultConfigDisabled) {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null && !defaultConfigDisabled) {
                    instance = createDefaultConfigInstance();
                    registerConfigBean();
                }
            }
        }
        return instance;
    }

    /**
     * Get the current system wide configuration. If there has not been set, it will return a default
     * {@link ConcurrentCompositeConfiguration} which contains a SystemConfiguration from Apache Commons
     * Configuration and a {@link DynamicURLConfiguration}.
     */
    public static AbstractConfiguration getConfigInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
                }
            }
        }
        return instance;
    }

    private static void registerConfigBean() {
        if (Boolean.getBoolean(DynamicPropertyFactory.ENABLE_JMX)) {
            try {
                configMBean = ConfigJMXManager.registerConfigMbean(instance);
            } catch (Exception e) {
                logger.error("Unable to register with JMX", e);
            }
        }
    }

    static synchronized void setDirect(AbstractConfiguration config) {
        if (instance != null) {
            Collection<ConfigurationListener> listeners = instance.getConfigurationListeners();
            // transfer listeners
            // transfer properties which are not in conflict with new configuration
            for (Iterator<String> i = instance.getKeys(); i.hasNext();) {
                String key = i.next();
                Object value = instance.getProperty(key);
                if (value != null && !config.containsKey(key)) {
                    config.setProperty(key, value);
                }
            }
            if (listeners != null) {
                for (ConfigurationListener listener : listeners) {
                    if (listener instanceof ExpandedConfigurationListenerAdapter
                            && ((ExpandedConfigurationListenerAdapter) listener)
                                    .getListener() instanceof DynamicProperty.DynamicPropertyListener) {
                        // no need to transfer the fast property listener as it should be set later
                        // with the new configuration
                        continue;
                    }
                    config.addConfigurationListener(listener);
                }
            }
        }
        ConfigurationManager.removeDefaultConfiguration();
        ConfigurationManager.instance = config;
        ConfigurationManager.customConfigurationInstalled = true;
        ConfigurationManager.registerConfigBean();
    }

    /**
     * Load properties from resource file into the system wide configuration
     * @param path path of the resource
     * @throws IOException
     */
    public static void loadPropertiesFromResources(String path) throws IOException {
        if (instance == null) {
            instance = getConfigInstance();
        }
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        URL url = loader.getResource(path);
        if (url == null) {
            throw new IOException("Cannot locate " + path + " as a classpath resource.");
        }
        Properties props = new Properties();
        InputStream fin = url.openStream();
        props.load(fin);
        fin.close();
        if (instance instanceof AggregatedConfiguration) {
            String name = getConfigName(url);
            ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
            config.loadProperties(props);
            ((AggregatedConfiguration) instance).addConfiguration(config, name);
        } else {
            ConfigurationUtils.loadProperties(props, instance);
        }
    }

    /**
     * Load resource configName.properties first. Then load configName-deploymentEnvironment.properties
     * into the system wide configuration. For example, if configName is "application", and deployment environment
     * is "test", this API will first load "application.properties", then load "application-test.properties" to
     * override any property that also exist in "application.properties". 
     * 
     * @param configName prefix of the properties file name.
     * @throws IOException
     * @see DeploymentContext#getDeploymentEnvironment()
     */
    public static void loadCascadedPropertiesFromResources(String configName) throws IOException {
        String defaultConfigFileName = configName + ".properties";
        if (instance == null) {
            instance = getConfigInstance();
        }
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        URL url = loader.getResource(defaultConfigFileName);
        if (url == null) {
            throw new IOException("Cannot locate " + defaultConfigFileName + " as a classpath resource.");
        }
        Properties props = getPropertiesFromFile(url);
        String environment = getDeploymentContext().getDeploymentEnvironment();
        if (environment != null && environment.length() > 0) {
            String envConfigFileName = configName + "-" + environment + ".properties";
            url = loader.getResource(envConfigFileName);
            if (url != null) {
                Properties envProps = getPropertiesFromFile(url);
                if (envProps != null) {
                    props.putAll(envProps);
                }
            }
        }
        if (instance instanceof AggregatedConfiguration) {
            ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
            config.loadProperties(props);
            ((AggregatedConfiguration) instance).addConfiguration(config, configName);
        } else {
            ConfigurationUtils.loadProperties(props, instance);
        }
    }

    /**
     * Load properties from the specified configuration into system wide configuration
     */
    public static void loadPropertiesFromConfiguration(AbstractConfiguration config) {
        if (instance instanceof AggregatedConfiguration) {
            ((AggregatedConfiguration) instance).addConfiguration(config);
        } else {
            Properties props = ConfigurationUtils.getProperties(config);
            ConfigurationUtils.loadProperties(props, instance);
        }
    }

    /**
     * Load the specified properties into system wide configuration
     */
    public static void loadProperties(Properties properties) {
        ConfigurationUtils.loadProperties(properties, instance);
    }

    public static void setDeploymentContext(DeploymentContext context) {
        ConfigurationManager.context = context;
        if (instance != null) {
            for (DeploymentContext.ContextKey key : DeploymentContext.ContextKey.values()) {
                String value = context.getValue(key);
                if (value != null) {
                    instance.setProperty(key.getKey(), value);
                }
            }
        }
    }

    public static DeploymentContext getDeploymentContext() {
        return context;
    }

    private static String getConfigName(URL propertyFile) {
        String name = propertyFile.toExternalForm();
        name = name.replace('\\', '/'); // Windows
        final String scheme = propertyFile.getProtocol().toLowerCase();
        if ("jar".equals(scheme) || "zip".equals(scheme)) {
            // Use the unqualified name of the jar file.
            final int bang = name.lastIndexOf("!");
            if (bang >= 0) {
                name = name.substring(0, bang);
            }
            final int slash = name.lastIndexOf("/");
            if (slash >= 0) {
                name = name.substring(slash + 1);
            }
        } else {
            // Use the URL of the enclosing directory.
            final int slash = name.lastIndexOf("/");
            if (slash >= 0) {
                name = name.substring(0, slash);
            }
        }
        return name;
    }

    private static synchronized void removeDefaultConfiguration() {
        if (instance == null || customConfigurationInstalled) {
            return;
        }
        ConcurrentCompositeConfiguration defaultConfig = (ConcurrentCompositeConfiguration) instance;
        // stop loading of the configuration
        DynamicURLConfiguration defaultFileConfig = (DynamicURLConfiguration) defaultConfig
                .getConfiguration(DynamicPropertyFactory.URL_CONFIG_NAME);
        if (defaultFileConfig != null) {
            defaultFileConfig.stopLoading();
        }
        Collection<ConfigurationListener> listeners = defaultConfig.getConfigurationListeners();

        // find the listener and remove it so that DynamicProperty will no longer receives 
        // callback from the default configuration source
        ConfigurationListener dynamicPropertyListener = null;
        for (ConfigurationListener l : listeners) {
            if (l instanceof ExpandedConfigurationListenerAdapter && ((ExpandedConfigurationListenerAdapter) l)
                    .getListener() instanceof DynamicProperty.DynamicPropertyListener) {
                dynamicPropertyListener = l;
                break;
            }
        }
        if (dynamicPropertyListener != null) {
            defaultConfig.removeConfigurationListener(dynamicPropertyListener);
        }
        if (configMBean != null) {
            try {
                ConfigJMXManager.unRegisterConfigMBean(defaultConfig, configMBean);
            } catch (Exception e) {
                logger.error("Error unregistering with JMX", e);
            }
        }
        instance = null;
    }

    public static AbstractConfiguration getConfigFromPropertiesFile(URL startingUrl) throws FileNotFoundException {
        return ConfigurationUtils.getConfigFromPropertiesFile(startingUrl, getLoadedPropertiesURLs(),
                PROP_NEXT_LOAD);
    }

    public static Properties getPropertiesFromFile(URL startingUrl) throws FileNotFoundException {
        return ConfigurationUtils.getPropertiesFromFile(startingUrl, getLoadedPropertiesURLs(), PROP_NEXT_LOAD);
    }

}