/**
 * Copyright 2014 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.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;

/**
 * This class delegates property read/write to an another configuration but is also attached with
 * a dynamic configuration source and polling scheduler so that its properties can be changed dynamically
 * at runtime. In other words, if the same property is defined in both the original configuration
 * and the dynamic configuration source, the value in the original configuration will be overridden.
 * <p>
 * This class can be served as a decorator to an existing configuration to make the property values
 * dynamic.
 *
 * @author awang
 *
 */
public class ConfigurationWithPollingSource implements Configuration {

    private final Configuration config;
    private final AbstractPollingScheduler scheduler;

    /**
     * Create an instance and start polling the configuration source
     *
     * @param config Configuration to delegate to
     * @param source {@link PolledConfigurationSource} to poll get new/changed properties
     * @param scheduler AbstractPollingScheduler to provide the polling schedule
     */
    public ConfigurationWithPollingSource(Configuration config, PolledConfigurationSource source,
            AbstractPollingScheduler scheduler) {
        this.config = config;
        this.scheduler = scheduler;
        scheduler.startPolling(source, this);
    }

    public final Configuration getConfiguration() {
        return config;
    }

    public final void stopPolling() {
        scheduler.stop();
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public void addProperty(String key, Object value) {
        config.addProperty(key, value);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public void clear() {
        config.clear();
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public void clearProperty(String key) {
        config.clearProperty(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public boolean containsKey(String arg0) {
        return config.containsKey(arg0);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
        return config.getBigDecimal(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public BigDecimal getBigDecimal(String key) {
        return config.getBigDecimal(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public BigInteger getBigInteger(String key, BigInteger defaultValue) {
        return config.getBigInteger(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public BigInteger getBigInteger(String key) {
        return config.getBigInteger(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public boolean getBoolean(String key, boolean defaultValue) {
        return config.getBoolean(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Boolean getBoolean(String key, Boolean defaultValue) {
        return config.getBoolean(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public boolean getBoolean(String key) {
        return config.getBoolean(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public byte getByte(String key, byte defaultValue) {
        return config.getByte(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Byte getByte(String key, Byte defaultValue) {
        return config.getByte(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public byte getByte(String key) {
        return config.getByte(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public double getDouble(String key, double defaultValue) {
        return config.getDouble(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Double getDouble(String key, Double defaultValue) {
        return config.getDouble(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public double getDouble(String key) {
        return config.getDouble(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public float getFloat(String key, float defaultValue) {
        return config.getFloat(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Float getFloat(String key, Float defaultValue) {
        return config.getFloat(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public float getFloat(String key) {
        return config.getFloat(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public int getInt(String key, int defaultValue) {
        return config.getInt(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public int getInt(String key) {
        return config.getInt(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Integer getInteger(String key, Integer defaultValue) {
        return config.getInteger(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Iterator getKeys() {
        return config.getKeys();
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Iterator getKeys(String prefix) {
        return config.getKeys(prefix);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public List getList(String key, List defaultValue) {
        return config.getList(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public List getList(String key) {
        return config.getList(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public long getLong(String key, long defaultValue) {
        return config.getLong(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Long getLong(String key, Long defaultValue) {
        return config.getLong(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public long getLong(String key) {
        return config.getLong(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Properties getProperties(String key) {
        return config.getProperties(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Object getProperty(String arg0) {
        return config.getProperty(arg0);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public short getShort(String key, short defaultValue) {
        return config.getShort(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Short getShort(String key, Short defaultValue) {
        return config.getShort(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public short getShort(String key) {
        return config.getShort(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public String getString(String key, String defaultValue) {
        return config.getString(key, defaultValue);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public String getString(String key) {
        return config.getString(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public String[] getStringArray(String key) {
        return config.getStringArray(key);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public boolean isEmpty() {
        return config.isEmpty();
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public void setProperty(String key, Object value) {
        config.setProperty(key, value);
    }

    /**
     * Delegates to the underlying configuration.
     */
    @Override
    public Configuration subset(String prefix) {
        return config.subset(prefix);
    }

}


/**
 * Copyright 2014 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 com.netflix.config.jmx.BaseConfigMBean;
import com.netflix.config.sources.URLConfigurationSource;
import org.apache.commons.configuration.AbstractConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A factory that creates instances of dynamic properties and associates them with an underlying configuration
 * or {@link DynamicPropertySupport} where the properties could be changed dynamically at runtime.
 *

 * It is recommended to initialize this class with a configuration or DynamicPropertySupport before the first call to
 * {@link #getInstance()}. Otherwise, it will be lazily initialized with a {@link ConcurrentCompositeConfiguration},
 * where a SystemConfiguration and {@link DynamicURLConfiguration} will be added. You can also disable installing the default configuration
 * by setting system property {@value #DISABLE_DEFAULT_CONFIG} to be true.
 *

 * If system property {@value #ENABLE_JMX} is set to true, when this class is initialized with a configuration,
 * the configuration will also be exposed to JMX via an instance of {@link BaseConfigMBean}, where you can update the properties
 * via jconsole.
 *

 * Example:

 *    import com.netflix.config.DynamicProperty;
 *

 *    class MyClass {
 *        private static DynamicIntProperty maxWaitMillis
 *            = DynamicPropertyFactory.getInstance().getIntProperty("myclass.sleepMillis", 250);
 *           // ...
 *

 *           // add a callback when this property is changed
 *           maxWaitMillis.addCallback(new Runnable() {
 *               public void run() {
 *                   int currentValue = maxWaitMillis.get();
 *                   // ...
 *               }
 *           });
 *           // ...
 *           // Wait for a configurable amount of time for condition to become true.
 *           // Note that the time can be changed on-the-fly.
 *           someCondition.wait(maxWaitMillis.get());
 *

 *        // ...
 *    }
 *

 *

 * Please note that you should not cache the property value if you expect the value to change on-the-fly. For example,
 * in the following code the change of the value is ineffective:
 *

 *

 *    int maxWaitMillis = DynamicPropertyFactory.getInstance().getIntProperty("myclass.sleepMillis", 250).get();
 *    // ...
 *    someCondition.wait(maxWaitMillis);
 *

 *
 * @author awang
 */
public class DynamicPropertyFactory {

    private static DynamicPropertyFactory instance = new DynamicPropertyFactory();
    private volatile static DynamicPropertySupport config = null;
    private volatile static boolean initializedWithDefaultConfig = false;
    private static final Logger logger = LoggerFactory.getLogger(DynamicPropertyFactory.class);

    /**
     * @deprecated Moved to ConfigurationManager in 0.5.12
     */
    @Deprecated
    public static final String URL_CONFIG_NAME = ConfigurationManager.URL_CONFIG_NAME;

    /**
     * @deprecated Moved to ConfigurationManager in 0.5.12
     */
    @Deprecated
    public static final String SYS_CONFIG_NAME = ConfigurationManager.SYS_CONFIG_NAME;

    /**
     * Boolean system property to define whether a configuration MBean should be registered with
     * JMX so that properties can be accessed via JMX console. Default is "unset".
     */
    public static final String ENABLE_JMX = "archaius.dynamicPropertyFactory.registerConfigWithJMX";

    /**
     * System property name that defines whether {@link #getInstance()} should throw
     * {@link MissingConfigurationSourceException} if there is no proper configuration source
     * at the time of call.
     */
    public static final String THROW_MISSING_CONFIGURATION_SOURCE_EXCEPTION =
            "archaius.dynamicProperty.throwMissingConfigurationSourceException";
    private volatile static boolean throwMissingConfigurationSourceException =
            Boolean.getBoolean(THROW_MISSING_CONFIGURATION_SOURCE_EXCEPTION);

    /**
     * System property to disable adding SystemConfiguration to the default ConcurrentCompositeConfiguration
     *
     * @deprecated Moved to ConfigurationManager in 0.5.12
     */
    @Deprecated
    public static final String DISABLE_DEFAULT_SYS_CONFIG = ConfigurationManager.DISABLE_DEFAULT_SYS_CONFIG;

    /**
     * System property to determine whether DynamicPropertyFactory should be lazily initialized with
     * default configuration for {@link #getInstance()}. Default is false (not set).
     */
    public static final String DISABLE_DEFAULT_CONFIG = "archaius.dynamicProperty.disableDefaultConfig";

    private DynamicPropertyFactory() {
    }

    /**
     * Initialize the factory with an AbstractConfiguration.
     *

     * The initialization will register a ConfigurationListener with the configuration so that {@link DynamicProperty}
     * will receives a callback and refresh its value when a property is changed in the configuration.
     *

     * If the factory is already initialized with a default configuration source (see {@link #getInstance()}), it will re-register
     * itself with the new configuration source passed to this method. Otherwise, this method will throw IllegalArgumentException
     * if it has been initialized with a different and non-default configuration source. This method should be only called once.
     *
     * @param config Configuration to be attached with DynamicProperty
     * @return the instance of DynamicPropertyFactory
     * @throws IllegalArgumentException if the factory has already been initialized with a non-default configuration source
     */
    public static DynamicPropertyFactory initWithConfigurationSource(AbstractConfiguration config) {
        synchronized (ConfigurationManager.class) {
            if (config == null) {
                throw new NullPointerException("config is null");
            }
            if (ConfigurationManager.isConfigurationInstalled() && config != ConfigurationManager.instance) {
                throw new IllegalStateException("ConfigurationManager is already initialized with configuration "
                        + ConfigurationManager.getConfigInstance());
            }
            if (config instanceof DynamicPropertySupport) {
                return initWithConfigurationSource((DynamicPropertySupport) config);
            }
            return initWithConfigurationSource(new ConfigurationBackedDynamicPropertySupportImpl(config));
        }
    }

    /**
     * Return whether the factory is initialized with the default ConcurrentCompositeConfiguration.
     */
    public static boolean isInitializedWithDefaultConfig() {
        return initializedWithDefaultConfig;
    }

    /**
     * Get the backing configuration from the factory. This can be cased to a {@link ConcurrentCompositeConfiguration}
     * if the default configuration is installed.
     *
For example:
     *

     *     Configuration config = DynamicPropertyFactory.getInstance().getBackingConfigurationSource();
     *     if (DynamicPropertyFactory.isInitializedWithDefaultConfig()) {
     *         ConcurrentCompositeConfiguration composite = (ConcurrentCompositeConfiguration) config;
     *         // ...
     *     }
     *

     *
     * @return the configuration source that DynamicPropertyFactory is initialized with. This will be an instance of
     *         Configuration, or {@link DynamicPropertySupport}, or null if DynamicPropertyFactory has not been initialized.
     */
    public static Object getBackingConfigurationSource() {
        if (config instanceof ConfigurationBackedDynamicPropertySupportImpl) {
            return ((ConfigurationBackedDynamicPropertySupportImpl) config).getConfiguration();
        } else {
            return config;
        }
    }

    /**
     * Set the boolean value to indicate whether {@link #getInstance()} should throw
     * {@link MissingConfigurationSourceException} if there is no proper configuration source
     * at the time of call.
     *
     * @param value to set
     */
    public static void setThrowMissingConfigurationSourceException(boolean value) {
        throwMissingConfigurationSourceException = value;
    }

    /**
     * @return the boolean value to indicate whether {@link #getInstance()} should throw
     *         {@link MissingConfigurationSourceException} if there is no proper configuration source
     *         at the time of call.
     */
    public static boolean isThrowMissingConfigurationSourceException() {
        return throwMissingConfigurationSourceException;
    }

    /**
     * Initialize the factory with a {@link DynamicPropertySupport}.
     *

     * The initialization will register a {@link PropertyListener} with the DynamicPropertySupport so that DynamicProperty
     * will receives a callback and refresh its value when a property is changed.
     *

     * If the factory is already initialized with a default configuration source (see {@link #getInstance()}), it will re-register
     * itself with the new configuration source passed to this method. Otherwise, this method will throw IllegalArgumentException
     * if it has been initialized with a non-default configuration source. This method should be only called once.
     *
     * @param dynamicPropertySupport DynamicPropertySupport to be associated with the DynamicProperty
     * @return the instance of DynamicPropertyFactory
     * @throws IllegalArgumentException if the factory has already been initialized with a different and non-default configuration source
     */
    public static DynamicPropertyFactory initWithConfigurationSource(DynamicPropertySupport dynamicPropertySupport) {
        synchronized (ConfigurationManager.class) {
            if (dynamicPropertySupport == null) {
                throw new IllegalArgumentException("dynamicPropertySupport is null");
            }
            AbstractConfiguration configuration = null;
            if (dynamicPropertySupport instanceof AbstractConfiguration) {
                configuration = (AbstractConfiguration) dynamicPropertySupport;
            } else if (dynamicPropertySupport instanceof ConfigurationBackedDynamicPropertySupportImpl) {
                configuration = ((ConfigurationBackedDynamicPropertySupportImpl) dynamicPropertySupport).getConfiguration();
            }
            if (initializedWithDefaultConfig) {
                config = null;
            } else if (config != null && config != dynamicPropertySupport) {
                throw new IllegalStateException("DynamicPropertyFactory is already initialized with a diffrerent configuration source: " + config);
            }
            if (ConfigurationManager.isConfigurationInstalled()
                    && (configuration != null && configuration != ConfigurationManager.instance)) {
                throw new IllegalStateException("ConfigurationManager is already initialized with configuration "
                        + ConfigurationManager.getConfigInstance());
            }
            if (configuration != null && configuration != ConfigurationManager.instance) {
                ConfigurationManager.setDirect(configuration);
            }
            setDirect(dynamicPropertySupport);
            return instance;
        }
    }

    static void setDirect(DynamicPropertySupport support) {
        synchronized (ConfigurationManager.class) {
            config = support;
            DynamicProperty.registerWithDynamicPropertySupport(support);
            initializedWithDefaultConfig = false;
        }
    }

    /**
     * Get the instance to create dynamic properties. If the factory is not initialized with a configuration source
     * (see {@link #initWithConfigurationSource(AbstractConfiguration)} and {@link #initWithConfigurationSource(DynamicPropertySupport)}),
     * it will fist try to initialize itself with a default {@link ConcurrentCompositeConfiguration}, with the following two
     * sub configurations:
     *

     *
A SystemConfiguration, which contains all the system properties. You can disable adding this Configuration by setting
     * system property {@value #DISABLE_DEFAULT_SYS_CONFIG} to true
     *
A  {@link DynamicURLConfiguration}, which at a fixed interval polls
     * a configuration file (see {@link URLConfigurationSource#DEFAULT_CONFIG_FILE_NAME}) on classpath and a set of URLs specified via a system property
     * (see {@link URLConfigurationSource#CONFIG_URL}).
     *

     * Between the two sub-configurations, the SystemConfiguration will take the precedence when determining property values.
     *

     * You can disable the initialization with the default configuration by setting system property {@value #DISABLE_DEFAULT_CONFIG} to "true".
     */
    public static DynamicPropertyFactory getInstance() {
        if (config == null) {
            synchronized (ConfigurationManager.class) {
                if (config == null) {
                    AbstractConfiguration configFromManager = ConfigurationManager.getConfigInstance();
                    if (configFromManager != null) {
                        initWithConfigurationSource(configFromManager);
                        initializedWithDefaultConfig = !ConfigurationManager.isConfigurationInstalled();
                        logger.info("DynamicPropertyFactory is initialized with configuration sources: " + configFromManager);
                    }
                }
            }
        }
        return instance;
    }

    private static void checkAndWarn(String propName) {
        if (config == null) {
            logger.warn("DynamicProperty " + propName + " is created without a configuration source for callback.");
        }
    }

    /**
     * Create a new property whose value is a string and subject to change on-the-fly.
     *
     * @param propName     property name
     * @param defaultValue default value if the property is not defined in underlying configuration
     */
    public DynamicStringProperty getStringProperty(String propName, String defaultValue) {
        return getStringProperty(propName, defaultValue, null);
    }

    /**
     * Create a new property whose value is a string and subject to change on-the-fly.
     *
     * @param propName               property name
     * @param defaultValue           default value if the property is not defined in underlying configuration
     * @param propertyChangeCallback a Runnable to be called when the property is changed
     */
    public DynamicStringProperty getStringProperty(String propName, String defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicStringProperty property = new DynamicStringProperty(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }

    /**
     * Create a new property whose value is an integer and subject to change on-the-fly..
     *
     * @param propName     property name
     * @param defaultValue default value if the property is not defined in underlying configuration
     */
    public DynamicIntProperty getIntProperty(String propName, int defaultValue) {
        return getIntProperty(propName, defaultValue, null);
    }

    /**
     * Create a new property whose value is an integer and subject to change on-the-fly.
     *
     * @param propName               property name
     * @param defaultValue           default value if the property is not defined in underlying configuration
     * @param propertyChangeCallback a Runnable to be called when the property is changed
     */
    public DynamicIntProperty getIntProperty(String propName, int defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicIntProperty property = new DynamicIntProperty(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }

    /**
     * Create a new property whose value is a long and subject to change on-the-fly..
     *
     * @param propName     property name
     * @param defaultValue default value if the property is not defined in underlying configuration
     */
    public DynamicLongProperty getLongProperty(String propName, long defaultValue) {
        return getLongProperty(propName, defaultValue, null);
    }

    /**
     * Create a new property whose value is a long and subject to change on-the-fly.
     *
     * @param propName               property name
     * @param defaultValue           default value if the property is not defined in underlying configuration
     * @param propertyChangeCallback a Runnable to be called when the property is changed
     */
    public DynamicLongProperty getLongProperty(String propName, long defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicLongProperty property = new DynamicLongProperty(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }

    /**
     * Create a new property whose value is a boolean and subject to change on-the-fly..
     *
     * @param propName     property name
     * @param defaultValue default value if the property is not defined in underlying configuration
     */
    public DynamicBooleanProperty getBooleanProperty(String propName, boolean defaultValue) {
        return getBooleanProperty(propName, defaultValue, null);
    }

    /**
     * Create a new property whose value is a boolean and subject to change on-the-fly.
     *
     * @param propName               property name
     * @param defaultValue           default value if the property is not defined in underlying configuration
     * @param propertyChangeCallback a Runnable to be called when the property is changed
     */
    public DynamicBooleanProperty getBooleanProperty(String propName, boolean defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicBooleanProperty property = new DynamicBooleanProperty(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }

    /**
     * Create a new property whose value is a float and subject to change on-the-fly..
     *
     * @param propName     property name
     * @param defaultValue default value if the property is not defined in underlying configuration
     */
    public DynamicFloatProperty getFloatProperty(String propName, float defaultValue) {
        return getFloatProperty(propName, defaultValue, null);
    }

    /**
     * Create a new property whose value is a float and subject to change on-the-fly.
     *
     * @param propName               property name
     * @param defaultValue           default value if the property is not defined in underlying configuration
     * @param propertyChangeCallback a Runnable to be called when the property is changed
     */
    public DynamicFloatProperty getFloatProperty(String propName, float defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicFloatProperty property = new DynamicFloatProperty(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }

    /**
     * Create a new property whose value is a double and subject to change on-the-fly..
     *
     * @param propName     property name
     * @param defaultValue default value if the property is not defined in underlying configuration
     */
    public DynamicDoubleProperty getDoubleProperty(String propName, double defaultValue) {
        return getDoubleProperty(propName, defaultValue, null);
    }

    /**
     * Create a new property whose value is a double and subject to change on-the-fly.
     *
     * @param propName               property name
     * @param defaultValue           default value if the property is not defined in underlying configuration
     * @param propertyChangeCallback a Runnable to be called when the property is changed
     */
    public DynamicDoubleProperty getDoubleProperty(String propName, double defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicDoubleProperty property = new DynamicDoubleProperty(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }

    /**
     * Create a new contextual property of type T
     *
     * @param propName     property name
     * @param defaultValue default value if the property is not defined in underlying configuration
     * @param <T>          the type of the property value
     */
    public <T> DynamicContextualProperty<T> getContextualProperty(String propName, T defaultValue) {
        return getContextualProperty(propName, defaultValue, null);
    }

    /**
     * Create a new contextual property of type T
     *
     * @param propName               property name
     * @param defaultValue           default value if the property is not defined in underlying configuration
     * @param propertyChangeCallback a Runnable to be called when the property is changed
     * @param <T>                    the type of the property value
     */
    public <T> DynamicContextualProperty<T> getContextualProperty(String propName, T defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicContextualProperty<T> property = new DynamicContextualProperty<T>(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }

    private static void addCallback(Runnable callback, PropertyWrapper<?> wrapper) {
        if (callback != null) {
            wrapper.prop.addCallback(callback);
        }
    }

}


/**
 * Copyright 2014 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.sources;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.ScanResult;
import com.netflix.config.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * User: gorzell
 * Date: 1/17/13
 * Time: 10:18 AM
 * This leverages some of the semantics of the PollingSource in order to have one place where the full table scan from
 * Dynamo is cached.  It is mean to be consumed but a number of DeploymentContext aware sources to keep them from all
 * having to load the table separately.
 */
public class DynamoDbDeploymentContextTableCache extends AbstractDynamoDbConfigurationSource<PropertyWithDeploymentContext> {
    private static Logger log = LoggerFactory.getLogger(DynamoDbDeploymentContextTableCache.class);

    //Property names
    static final String contextKeyAttributePropertyName = "com.netflix.config.dynamo.contextKeyAttributeName";
    static final String contextValueAttributePropertyName = "com.netflix.config.dynamo.contextValueAttributeName";

    //Property defaults
    static final String defaultContextKeyAttribute = "contextKey";
    static final String defaultContextValueAttribute = "contextValue";

    //Dynamic Properties
    private final DynamicStringProperty contextKeyAttributeName = DynamicPropertyFactory.getInstance()
            .getStringProperty(contextKeyAttributePropertyName, defaultContextKeyAttribute);
    private final DynamicStringProperty contextValueAttributeName = DynamicPropertyFactory.getInstance()
            .getStringProperty(contextValueAttributePropertyName, defaultContextValueAttribute);

    // Delay defaults
    static final int defaultInitialDelayMillis = 30000;
    static final int defaultDelayMillis = 60000;

    private final int initialDelayMillis;
    private final int delayMillis;

    private ScheduledExecutorService executor;
    private volatile Map<String, PropertyWithDeploymentContext> cachedTable = new HashMap<String, PropertyWithDeploymentContext>();

    public DynamoDbDeploymentContextTableCache() {
        this(defaultInitialDelayMillis, defaultDelayMillis);
    }

    /**
     * @param initialDelayMillis
     * @param delayMillis
     */
    public DynamoDbDeploymentContextTableCache(int initialDelayMillis, int delayMillis) {
        super();
        this.initialDelayMillis = initialDelayMillis;
        this.delayMillis = delayMillis;
        start();
    }

    /**
     * @param clientConfiguration
     */
    public DynamoDbDeploymentContextTableCache(ClientConfiguration clientConfiguration) {
        this(clientConfiguration, defaultInitialDelayMillis, defaultDelayMillis);
    }

    /**
     * @param clientConfiguration
     * @param initialDelayMillis
     * @param delayMillis
     */
    public DynamoDbDeploymentContextTableCache(ClientConfiguration clientConfiguration, int initialDelayMillis, int delayMillis) {
        super(clientConfiguration);
        this.initialDelayMillis = initialDelayMillis;
        this.delayMillis = delayMillis;
        start();
    }

    /**
     * @param credentials
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentials credentials) {
        this(credentials, defaultInitialDelayMillis, defaultDelayMillis);
    }

    /**
     * @param credentials
     * @param initialDelayMillis
     * @param delayMillis
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentials credentials, int initialDelayMillis, int delayMillis) {
        super(credentials);
        this.initialDelayMillis = initialDelayMillis;
        this.delayMillis = delayMillis;
        start();
    }

    /**
     * @param credentials
     * @param clientConfiguration
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentials credentials, ClientConfiguration clientConfiguration) {
        this(credentials, clientConfiguration, defaultInitialDelayMillis, defaultDelayMillis);
    }

    /**
     * @param credentials
     * @param clientConfiguration
     * @param initialDelayMillis
     * @param delayMillis
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentials credentials, ClientConfiguration clientConfiguration, int initialDelayMillis, int delayMillis) {
        super(credentials, clientConfiguration);
        this.initialDelayMillis = initialDelayMillis;
        this.delayMillis = delayMillis;
        start();
    }

    /**
     * @param credentialsProvider
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentialsProvider credentialsProvider) {
        this(credentialsProvider, defaultInitialDelayMillis, defaultDelayMillis);
    }

    /**
     * @param credentialsProvider
     * @param initialDelayMillis
     * @param delayMillis
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentialsProvider credentialsProvider, int initialDelayMillis, int delayMillis) {
        super(credentialsProvider);
        this.initialDelayMillis = initialDelayMillis;
        this.delayMillis = delayMillis;
        start();
    }

    /**
     * @param credentialsProvider
     * @param clientConfiguration
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentialsProvider credentialsProvider, ClientConfiguration clientConfiguration) {
        this(credentialsProvider, clientConfiguration, defaultInitialDelayMillis, defaultDelayMillis);
    }

    /**
     * @param credentialsProvider
     * @param clientConfiguration
     * @param initialDelayMillis
     * @param delayMillis
     */
    public DynamoDbDeploymentContextTableCache(AWSCredentialsProvider credentialsProvider, ClientConfiguration clientConfiguration, int initialDelayMillis, int delayMillis) {
        super(credentialsProvider, clientConfiguration);
        this.initialDelayMillis = initialDelayMillis;
        this.delayMillis = delayMillis;
        start();
    }

    /**
     * @param dbClient
     */
    public DynamoDbDeploymentContextTableCache(AmazonDynamoDB dbClient) {
        this(dbClient, defaultInitialDelayMillis, defaultDelayMillis);
    }

    /**
     * @param dbClient
     * @param initialDelayMillis
     * @param delayMillis
     */
    public DynamoDbDeploymentContextTableCache(AmazonDynamoDB dbClient, int initialDelayMillis, int delayMillis) {
        super(dbClient);
        this.initialDelayMillis = initialDelayMillis;
        this.delayMillis = delayMillis;
        start();
    }

    private synchronized void schedule(Runnable runnable) {
        executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "pollingDynamoTableCache");
                t.setDaemon(true);
                return t;
            }
        });
        executor.scheduleWithFixedDelay(runnable, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS);
    }

    /**
     * Stop polling the source table
     */
    public void stop() {
        if (executor != null) {
            executor.shutdown();
            executor = null;
        }
    }

    private void start() {
        cachedTable = loadPropertiesFromTable(tableName.get());
        schedule(getPollingRunnable());
    }

    private Runnable getPollingRunnable() {
        return new Runnable() {
            public void run() {
                log.debug("Dynamo cached polling started");
                try {
                    Map<String, PropertyWithDeploymentContext> newMap = loadPropertiesFromTable(tableName.get());
                    cachedTable = newMap;
                } catch (Throwable e) {
                    log.error("Error getting result from polling source", e);
                    return;
                }
            }
        };
    }

    /**
     * Scan the table in dynamo and create a map with the results.  In this case the map has a complex type as the value,
     * so that Deployment Context is taken into account.
     *
     * @param table
     * @return
     */
    @Override
    protected Map<String, PropertyWithDeploymentContext> loadPropertiesFromTable(String table) {
        Map<String, PropertyWithDeploymentContext> propertyMap = new HashMap<String, PropertyWithDeploymentContext>();
        Map<String, AttributeValue> lastKeysEvaluated = null;
        do {
            ScanRequest scanRequest = new ScanRequest()
                    .withTableName(table)
                    .withExclusiveStartKey(lastKeysEvaluated);
            ScanResult result = dbScanWithThroughputBackOff(scanRequest);
            for (Map<String, AttributeValue> item : result.getItems()) {
                String keyVal = item.get(keyAttributeName.get()).getS();

                //Need to deal with the fact that these attributes might not exist
                DeploymentContext.ContextKey contextKey = item.containsKey(contextKeyAttributeName.get()) ? DeploymentContext.ContextKey.valueOf(item.get(contextKeyAttributeName.get()).getS()) : null;
                String contextVal = item.containsKey(contextValueAttributeName.get()) ? item.get(contextValueAttributeName.get()).getS() : null;
                String key = keyVal + ";" + contextKey + ";" + contextVal;
                propertyMap.put(key,
                        new PropertyWithDeploymentContext(
                                contextKey,
                                contextVal,
                                keyVal,
                                item.get(valueAttributeName.get()).getS()
                        ));
            }
            lastKeysEvaluated = result.getLastEvaluatedKey();
        } while (lastKeysEvaluated != null);
        return propertyMap;
    }

    /**
     * Get the current values in the cache.
     *
     * @return
     */
    public Collection<PropertyWithDeploymentContext> getProperties() {
        return cachedTable.values();
    }
}


/**
 * Copyright 2014 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.jmx;

import org.apache.commons.configuration.AbstractConfiguration;

import com.netflix.config.ConcurrentCompositeConfiguration;
import com.netflix.config.util.ConfigurationUtils;

/**
 * A basic implementation of a Config MBean that allows for operations on
 * properties contained in the AbstractConfiguration.
 *
 * @author stonse
 *
 */
public class BaseConfigMBean implements ConfigMBean {

	AbstractConfiguration config = null;

	public BaseConfigMBean(AbstractConfiguration config) {
		this.config = config;
	}

	@Override
	public Object obtainProperties() {
		return ConfigurationUtils.getProperties(config);
	}

	@Override
	public Object getProperty(String key) {
		return config.getProperty(key);
	}

	/**
	 * Calls config.setProperty(). If the underlying configuration
	 * is {@link ConcurrentCompositeConfiguration}, it calls {@link ConcurrentCompositeConfiguration#setOverrideProperty(String, Object)}
	 * instead.
	 */
	@Override
	public void updateProperty(String key, String value) {
	    if (config instanceof ConcurrentCompositeConfiguration) {
	        ((ConcurrentCompositeConfiguration) config).setOverrideProperty(key, value);
	    } else {
		    config.setProperty(key, value);
	    }
	}

    /**
     * Calls config.clearProperty(). If the underlying configuration
     * is {@link ConcurrentCompositeConfiguration}, it calls {@link ConcurrentCompositeConfiguration#clearOverrideProperty(String)}
     * instead.
     *
Warning: {@link ConcurrentCompositeConfiguration#clearOverrideProperty(String)} does not clear the
     * property with the whole {@link ConcurrentCompositeConfiguration}, if any other child configurations in it has the same property.
     */
	@Override
	public void clearProperty(String key) {
        if (config instanceof ConcurrentCompositeConfiguration) {
            ((ConcurrentCompositeConfiguration) config).clearOverrideProperty(key);
        } else {
		    config.clearProperty(key);
        }
	}

    /**
     * Calls config.addrProperty(). If the underlying configuration
     * is {@link ConcurrentCompositeConfiguration}, it calls {@link ConcurrentCompositeConfiguration#setOverrideProperty(String, Object)}
     * instead.
     */
	@Override
	public void addProperty(String key, String value) {
        if (config instanceof ConcurrentCompositeConfiguration) {
            ((ConcurrentCompositeConfiguration) config).setOverrideProperty(key, value);
        } else {
		    config.addProperty(key, value);
        }
	}

}


/**
 * Copyright 2014 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 com.google.common.base.Preconditions;

import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;

/**
 * An implementation of {@link DeploymentContext} based on system wide configuration set with
 * {@link ConfigurationManager}. All the getters will first consult corresponding property
 * and return the value if set.
 *
 * @author awang
 */
public class ConfigurationBasedDeploymentContext extends SimpleDeploymentContext {

    /**
     * should use value from {@link ContextKey#getKey()} on {@link ContextKey#environment} as the property to set or get
     */
    @Deprecated
    public static final String DEPLOYMENT_ENVIRONMENT_PROPERTY = "archaius.deployment.environment";

    /**
     * should use value from {@link ContextKey#getKey()} on {@link ContextKey#datacenter} as the property to set or get
     */
    @Deprecated
    public static final String DEPLOYMENT_DATACENTER_PROPERTY = "archaius.deployment.datacenter";

    /**
     * should use value from {@link ContextKey#getKey()} on {@link ContextKey#appId} as the property to set or get
     */
    @Deprecated
    public static final String DEPLOYMENT_APPLICATION_ID_PROPERTY = "archaius.deployment.applicationId";

    /**
     * should use value from {@link ContextKey#getKey()} on {@link ContextKey#serverId} as the property to set or get
     */
    @Deprecated
    public static final String DEPLOYMENT_SERVER_ID_PROPERTY = "archaius.deployment.serverId";

    /**
     * should use value from {@link ContextKey#getKey()} on {@link ContextKey#stack} as the property to set or get
     */
    @Deprecated
    public static final String DEPLOYMENT_STACK_PROPERTY = "archaius.deployment.stack";

    /**
     * should use value from {@link ContextKey#getKey()} on {@link ContextKey#environment} as the property to set or get
     */
    @Deprecated
    public static final String DEPLOYMENT_REGION_PROPERTY = "archaius.deployment.region";

    private ConfigurationListener configListener = new ConfigurationListener() {

        @Override
        public void configurationChanged(ConfigurationEvent event) {
            if (event.isBeforeUpdate()
                    || (event.getType() != AbstractConfiguration.EVENT_ADD_PROPERTY
                            && event.getType() != AbstractConfiguration.EVENT_SET_PROPERTY)) {
                return;
            }
            String name = event.getPropertyName();
            String value = event.getPropertyValue() == null ? null : String.valueOf(event.getPropertyValue());
            if (value == null) {
                return;
            }
            if (name.equals(DEPLOYMENT_ENVIRONMENT_PROPERTY)) {
                ConfigurationBasedDeploymentContext.super.setDeploymentRegion(value);
                setValueInConfig(ContextKey.environment.getKey(), value);
            } else if (name.equals(DEPLOYMENT_DATACENTER_PROPERTY)) {
                ConfigurationBasedDeploymentContext.super.setDeploymentDatacenter(value);
                setValueInConfig(ContextKey.datacenter.getKey(), value);
            } else if (name.equals(DEPLOYMENT_STACK_PROPERTY)) {
                ConfigurationBasedDeploymentContext.super.setDeploymentStack(value);
                setValueInConfig(ContextKey.stack.getKey(), value);
            } else if (name.equals(DEPLOYMENT_APPLICATION_ID_PROPERTY)) {
                ConfigurationBasedDeploymentContext.super.setApplicationId(value);
                setValueInConfig(ContextKey.appId.getKey(), value);
            } else if (name.equals(DEPLOYMENT_REGION_PROPERTY)) {
                ConfigurationBasedDeploymentContext.super.setDeploymentRegion(value);
                setValueInConfig(ContextKey.region.getKey(), value);
            } else if (name.equals(DEPLOYMENT_SERVER_ID_PROPERTY)) {
                ConfigurationBasedDeploymentContext.super.setDeploymentServerId(value);
                setValueInConfig(ContextKey.serverId.getKey(), value);
            }
        }
    };

    public ConfigurationBasedDeploymentContext() {
        super();
        AbstractConfiguration config = ConfigurationManager.getConfigInstance();
        if (config != null) {
            String contextValue = getValueFromConfig(DEPLOYMENT_APPLICATION_ID_PROPERTY);
            if (contextValue != null) {
                setApplicationId(contextValue);
            }
            contextValue = getValueFromConfig(DEPLOYMENT_DATACENTER_PROPERTY);
            if (contextValue != null) {
                setDeploymentDatacenter(contextValue);
            }
            contextValue = getValueFromConfig(DEPLOYMENT_ENVIRONMENT_PROPERTY);
            if (contextValue != null) {
                setDeploymentEnvironment(contextValue);
            }
            contextValue = getValueFromConfig(DEPLOYMENT_REGION_PROPERTY);
            if (contextValue != null) {
                setDeploymentRegion(contextValue);
            }
            contextValue = getValueFromConfig(DEPLOYMENT_STACK_PROPERTY);
            if (contextValue != null) {
                setDeploymentStack(contextValue);
            }
            contextValue = getValueFromConfig(DEPLOYMENT_SERVER_ID_PROPERTY);
            if (contextValue != null) {
                setDeploymentServerId(contextValue);
            }
            config.addConfigurationListener(configListener);
        }
    }

    /**
     * Get the deployment environment. If property "archaius.deployment.environment"
     * is set in the system wide configuration, it will return it. Otherwise, it will return super.getDeploymentEnvironment().
     */
    @Override
    public String getDeploymentEnvironment() {
        String value = getValueFromConfig(DeploymentContext.ContextKey.environment.getKey());
        if (value != null) {
            return value;
        } else {
            value = getValueFromConfig(DEPLOYMENT_ENVIRONMENT_PROPERTY);
            if (value != null) {
                return value;
            } else {
                return super.getDeploymentEnvironment();
            }
        }
    }

    /**
     * Call super and also update the configuration to reflect the changes.
     *
     * @param value
     */
    @Override
    public void setDeploymentEnvironment(String value) {
        super.setDeploymentEnvironment(value);
        setValueInConfig(DEPLOYMENT_ENVIRONMENT_PROPERTY, value);
        setValueInConfig(ContextKey.environment.getKey(), value);
    }

    /**
     * Get the deployment environment. If property "archaius.deployment.datacenter"
     * is set in the system wide configuration, it will return it. Otherwise, it will return super.getDeploymentDatacenter().
     */
    @Override
    public String getDeploymentDatacenter() {
        String value = getValueFromConfig(DeploymentContext.ContextKey.datacenter.getKey());
        if (value != null) {
            return value;
        } else {
            value = getValueFromConfig(DEPLOYMENT_DATACENTER_PROPERTY);
        }
        if (value != null) {
            return value;
        } else {
            return super.getDeploymentDatacenter();
        }
    }

    /**
     * Call super and also update the configuration to reflect the changes.
     *
     * @param value
     */
    @Override
    public void setDeploymentDatacenter(String value) {
        super.setDeploymentDatacenter(value);
        setValueInConfig(DEPLOYMENT_DATACENTER_PROPERTY, value);
        setValueInConfig(ContextKey.datacenter.getKey(), value);
    }

    /**
     * Get the deployment environment. If property "archaius.deployment.applicationId"
     * is set in the system wide configuration, it will return it. Otherwise, it will return super.getApplicationId().
     */
    @Override
    public String getApplicationId() {
        String value = getValueFromConfig(DeploymentContext.ContextKey.appId.getKey());
        if (value != null) {
            return value;
        } else {
            value = getValueFromConfig(DEPLOYMENT_APPLICATION_ID_PROPERTY);
        }
        if (value != null) {
            return value;
        } else {
            return super.getApplicationId();
        }
    }

    /**
     * Call super and also update the configuration to reflect the changes.
     *
     * @param value
     */
    @Override
    public void setApplicationId(String value) {
        super.setApplicationId(value);
        setValueInConfig(DEPLOYMENT_APPLICATION_ID_PROPERTY, value);
        setValueInConfig(ContextKey.appId.getKey(), value);
    }

    /**
     * Get the deployment environment. If property "archaius.deployment.serverId"
     * is set in the system wide configuration, it will return it. Otherwise, it will return super.getDeploymentServerId().
     */
    @Override
    public String getDeploymentServerId() {
        String value = getValueFromConfig(DeploymentContext.ContextKey.serverId.getKey());
        if (value != null) {
            return value;
        } else {
            value = getValueFromConfig(DEPLOYMENT_SERVER_ID_PROPERTY);
        }
        if (value != null) {
            return value;
        } else {
            return super.getDeploymentServerId();
        }
    }

    /**
     * Call super and also update the configuration to reflect the changes.
     *
     * @param value
     */
    @Override
    public void setDeploymentServerId(String value) {
        super.setDeploymentServerId(value);
        setValueInConfig(DEPLOYMENT_SERVER_ID_PROPERTY, value);
        setValueInConfig(ContextKey.serverId.getKey(), value);
    }

    /**
     * Get the deployment environment. If property "archaius.deployment.stack"
     * is set in the system wide configuration, it will return it. Otherwise, it will return super.getDeploymentStack().
     */
    @Override
    public String getDeploymentStack() {
        String value = getValueFromConfig(DeploymentContext.ContextKey.stack.getKey());
        if (value != null) {
            return value;
        } else {
            value = getValueFromConfig(DEPLOYMENT_STACK_PROPERTY);
        }
        if (value != null) {
            return value;
        } else {
            return super.getDeploymentStack();
        }
    }

    /**
     * Call super and also update the configuration to reflect the changes.
     *
     * @param value
     */
    @Override
    public void setDeploymentStack(String value) {
        super.setDeploymentStack(value);
        setValueInConfig(DEPLOYMENT_STACK_PROPERTY, value);
        setValueInConfig(ContextKey.stack.getKey(), value);
    }

    /**
     * Get the deployment environment. If property "archaius.deployment.region"
     * is set in the system wide configuration, it will return it. Otherwise, it will return super.getDeploymentRegion().
     */
    @Override
    public String getDeploymentRegion() {
        String value = getValueFromConfig(DeploymentContext.ContextKey.region.getKey());
        if (value != null) {
            return value;
        } else {
            value = getValueFromConfig(DEPLOYMENT_REGION_PROPERTY);
        }
        if (value != null) {
            return value;
        } else {
            return super.getDeploymentRegion();
        }
    }

    /**
     * Call super and also update the configuration to reflect the changes.
     *
     * @param value
     */
    @Override
    public void setDeploymentRegion(String value) {
        super.setDeploymentRegion(value);
        setValueInConfig(DEPLOYMENT_REGION_PROPERTY, value);
        setValueInConfig(ContextKey.region.getKey(), value);
    }

    private String getValueFromConfig(String name) {
        Configuration conf = ConfigurationManager.getConfigInstance();
        if (conf == null) {
            return null;
        }
        String value = (String) conf.getProperty(name);
        if (value != null) {
            return value;
        } else {
            value = System.getProperty(name);
            if (value != null) {
                return value;
            }
        }
        return null;
    }

    private void setValueInConfig(String name, String value) {
        Preconditions.checkNotNull(name);
        Preconditions.checkNotNull(value);
        ConfigurationManager.getConfigInstance().setProperty(name, value);
    }

    @Override
    public String getValue(ContextKey key) {
        String value = getValueFromConfig(key.getKey());
        if (value != null) {
            return value;
        } else {
            return super.getValue(key);
        }
    }

}


/**
 * Copyright 2014 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.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.netflix.config.validation.PropertyChangeValidator;
import com.netflix.config.validation.ValidationException;

/**
 * A cached configuration property value that is automatically
 * updated when the config is changed.
 * The object is fully thread safe, and access is very fast.
 * (In fact, testing indicates that using a DynamicProperty is faster
 * than fetching a System Property.)
 *

 * This class is intended for those situations where the value of
 * a property is fetched many times, and the value may be
 * changed on-the-fly.
 * If the property is being read only once, "normal" access methods
 * should be used.
 * If the property value is fixed, consider just caching the value
 * in a variable.
 *

 * Fetching the cached value is synchronized only on this property,
 * so contention should be negligible.
 * If even that level of overhead is too much for you,
 * you should (a) think real hard about what you are doing, and
 * (b) just cache the property value in a variable and be done
 * with it.
 *

 * IMPORTANT NOTE
 *

 * DynamicProperty objects are not subject to normal garbage collection.
 * They should be used only as a static value that lives for the
 * lifetime of the program.
 *
 * @author slanning
 */
public class DynamicProperty {

    private static final Logger logger = LoggerFactory.getLogger(DynamicProperty.class);
    private volatile static DynamicPropertySupport dynamicPropertySupportImpl;

    /*
     * Cache update is handled by a single configuration listener,
     * with a static collection holding all defined DynamicProperty objects.
     * It is assumed that DynamicProperty objects are static and never
     * subject to gc, so holding them in the collection does not cause
     * a memory leak.
     */

    private static final ConcurrentHashMap<String, DynamicProperty> ALL_PROPS
        = new ConcurrentHashMap<String, DynamicProperty>();

    private Object lock = new Object();         // synchs caches and updates
    private String propName;
    private String stringValue = null;
    private long changedTime;
    private CopyOnWriteArraySet<Runnable> callbacks = new CopyOnWriteArraySet<Runnable>();
    private CopyOnWriteArraySet<PropertyChangeValidator> validators = new CopyOnWriteArraySet<PropertyChangeValidator>();

    /**
     * A cached value of a particular type.
     * @param <T> the type of the cached value
     */
    private abstract class CachedValue<T> {
        private volatile boolean isCached;
        private volatile IllegalArgumentException exception;
        private volatile T value;
        public CachedValue() {
            flush();
        }
        /**
         * Flushes the cached value.
         * Must be called with the {@code lock} variable held by this thread.
         */
        final void flush() {
            // NOTE: is only called from updateValue(Object) which holds the lock
            // assert(Thread.holdsLock(lock));
            isCached = false;
            exception = null;
            value = null;
        }
        /**
         * Gets the cached value.
         * If the value has not yet been parsed from the string value,
         * parse it now.
         * @return the parsed value, or null if there was no string value
         * @throws IllegalArgumentException if there was a problem
         */
        public T getValue() throws IllegalArgumentException {
            // Not quite double-check locking -- since isCached is marked as volatile
            if (!isCached) {
                synchronized (lock) {
                    try {
                        value = (stringValue == null) ? null : parse(stringValue);
                        exception = null;
                    } catch (Exception e) {
                        value = null;
                        exception = new IllegalArgumentException(e);
                    } finally {
                        isCached = true;
                    }
                }
            }
            if (exception != null) {
                throw exception;
            } else {
                return value;
            }
        }

        /**
         * Gets the cached value.
         * If the value has not yet been parsed from the string value,
         * parse it now.
         * If there is no string value, or there was a parse error,
         * returns the given default value.
         * @param defaultValue the value to return if there was a problem
         * @return the parsed value, or the default if there was no
         *    string value or a problem during parse
         */
        public T getValue(T defaultValue) {
            try {
                T result = getValue();
                return (result == null) ? defaultValue : result;
            } catch (IllegalArgumentException e) {
                return defaultValue;
            }
        }
        @Override
        public String toString() {
            if (!isCached) {
                return "{not cached}";
            } else if (exception != null) {
                return "{Exception: " + exception + "}";
            } else {
                return "{Value: " + value + "}";
            }
        }
        /**
         * Parse a string, converting it to an object of the value type.
         * @param rep the string representation to parse
         * @returns the parsed value
         * @throws Exception if the parse failed
         */
        protected abstract T parse(String rep) throws Exception;
    }

    private static final String[] TRUE_VALUES =  { "true",  "t", "yes", "y", "on"  };
    private static final String[] FALSE_VALUES = { "false", "f", "no",  "n", "off" };

    /*
     * Cached translated values
     */

    private CachedValue<Boolean> booleanValue = new CachedValue<Boolean>() {
        protected Boolean parse(String rep) throws IllegalArgumentException {
            for (int i = 0; i < TRUE_VALUES.length; i++){
                if (rep.equalsIgnoreCase(TRUE_VALUES[i])) {
                    return Boolean.TRUE;
                }
            }
            for (int i = 0; i < FALSE_VALUES.length; i++){
                if (rep.equalsIgnoreCase(FALSE_VALUES[i])) {
                    return Boolean.FALSE;
                }
            }
            throw new IllegalArgumentException();
        }
    };

    private CachedValue<String> cachedStringValue = new CachedValue<String>() {
        protected String parse(String rep) {
            return rep;
        }
    };

    private CachedValue<Integer> integerValue = new CachedValue<Integer>() {
        protected Integer parse(String rep) throws NumberFormatException {
            return Integer.valueOf(rep);
        }
    };

    private CachedValue<Long> longValue = new CachedValue<Long>() {
        protected Long parse(String rep) throws NumberFormatException {
            return Long.valueOf(rep);
        }
    };

    private CachedValue<Float> floatValue = new CachedValue<Float>() {
        protected Float parse(String rep) throws NumberFormatException {
            return Float.valueOf(rep);
        }
    };

    private CachedValue<Double> doubleValue = new CachedValue<Double>() {
        protected Double parse(String rep) throws NumberFormatException {
            return Double.valueOf(rep);
        }
    };

    private CachedValue<Class> classValue = new CachedValue<Class>() {
        protected Class parse(String rep) throws ClassNotFoundException {
            return Class.forName(rep);
        }
    };

    /*
     * Constructors
     */

    /**
     * Gets the DynamicProperty for a given property name.
     * This may be a previously constructed object,
     * or an object constructed on-demand to satisfy the request.
     *
     * @param propName the name of the property
     * @return a DynamicProperty object that holds the cached value of
     *    the configuration property named {@code propName}
     */
    public static DynamicProperty getInstance(String propName) {
        // This is to ensure that a configuration source is registered with
        // DynamicProperty
        if (dynamicPropertySupportImpl == null) {
            DynamicPropertyFactory.getInstance();
        }
        DynamicProperty prop = ALL_PROPS.get(propName);
        if (prop == null) {
            prop = new DynamicProperty(propName);
            DynamicProperty oldProp = ALL_PROPS.putIfAbsent(propName, prop);
            if (oldProp != null) {
                prop = oldProp;
            }
        }
        return prop;
    }

    protected DynamicProperty() {
    }
    /**
     * Create a new DynamicProperty with a given property name.
     *
     * @param propName the name of the property
     */
    private DynamicProperty(String propName) {
        this.propName = propName;
        updateValue();
    }

    /*
     * Accessor
     */

    /**
     * Gets the name of the property.
     */
    public String getName() {
        return propName;
    }

    /**
     * Gets the time (in milliseconds since the epoch)
     * when the property value was last set/changed.
     */
    public long getChangedTimestamp() {
        synchronized (lock) {
            return changedTime;
        }
    }

    /**
     * Gets the current value of the property as a String.
     *
     * @return the current property value, or null if there is none
     */
    public String getString() {
        return cachedStringValue.getValue();
    }

    /**
     * Gets the current value of the property as a String.
     *
     * @param defaultValue the value to return if the property is not defined
     * @return the current property value, or the default value there is none
     */
    public String getString(String defaultValue) {
        return cachedStringValue.getValue(defaultValue);
    }

    /**
     * Gets the current value of the property as an Boolean.
     * A property string value of "true", "yes", "on", "t" or "y"
     * produces {@code Boolean.TRUE}.
     * A property string value of "false", "no", "off", "f" or "b"
     * produces {@code Boolean.FALSE}.
     * (The value comparison ignores case.)
     * Any other value will result in an exception.
     *
     * @return the current property value, or null if there is none.
     * @throws IllegalArgumentException if the property is defined but
     *    is not an Boolean
     */
    public Boolean getBoolean() throws IllegalArgumentException {
        return booleanValue.getValue();
    }

    /**
     * Gets the current value of the property as an Boolean.
     *
     * @param defaultValue the value to return if the property is not defined,
     *    or is not of the proper format
     * @return the current property value, or the default value if there is
     *    none or the property is not of the proper format
     */
    public Boolean getBoolean(Boolean defaultValue) {
        return booleanValue.getValue(defaultValue);
    }

    /**
     * Gets the current value of the property as an Integer.
     *
     * @return the current property value, or null if there is none.
     * @throws IllegalArgumentException if the property is defined but
     *    is not an Integer
     */
    public Integer getInteger() throws IllegalArgumentException {
        return integerValue.getValue();
    }

    /**
     * Gets the current value of the property as an Integer.
     *
     * @param defaultValue the value to return if the property is not defined,
     *    or is not of the proper format
     * @return the current property value, or the default value if there is
     *    none or the property is not of the proper format
     */
    public Integer getInteger(Integer defaultValue) {
        return integerValue.getValue(defaultValue);
    }

    /**
     * Gets the current value of the property as a Float.
     *
     * @return the current property value, or null if there is none.
     * @throws IllegalArgumentException if the property is defined but is
     *    not a Float
     */
    public Float getFloat() throws IllegalArgumentException {
        return floatValue.getValue();
    }

    /**
     * Gets the current value of the property as a Float.
     *
     * @param defaultValue the value to return if the property is not defined,
     *    or is not of the proper format
     * @return the current property value, or the default value if there is
     *    none or the property is not of the proper format
     */
    public Float getFloat(Float defaultValue) {
        return floatValue.getValue(defaultValue);
    }

    /**
     * Gets the current value of the property as a Long.
     *
     * @return the current property value, or null if there is none.
     * @throws IllegalArgumentException if the property is defined but is
     *    not a Long
     */
    public Long getLong() throws IllegalArgumentException {
        return longValue.getValue();
    }

    /**
     * Gets the current value of the property as a Long.
     *
     * @param defaultValue the value to return if the property is not defined,
     *    or is not of the proper format
     * @return the current property value, or the default value if there is
     *    none or the property is not of the proper format
     */
    public Long getLong(Long defaultValue) {
        return longValue.getValue(defaultValue);
    }

    /**
     * Gets the current value of the property as a Long.
     *
     * @return the current property value, or null if there is none.
     * @throws IllegalArgumentException if the property is defined but is
     *    not a Long
     */
    public Double getDouble() throws IllegalArgumentException {
        return doubleValue.getValue();
    }

    /**
     * Gets the current value of the property as a Long.
     *
     * @param defaultValue the value to return if the property is not defined,
     *    or is not of the proper format
     * @return the current property value, or the default value if there is
     *    none or the property is not of the proper format
     */
    public Double getDouble(Double defaultValue) {
        return doubleValue.getValue(defaultValue);
    }

    /**
     * Gets the current value of the property as a Class.
     *
     * @return the current property value, or null if there is none.
     * @throws IllegalArgumentException zif the property is defined but is
     *    not the name of a Class
     */
    public Class getNamedClass() throws IllegalArgumentException {
        return classValue.getValue();
    }

    /**
     * Gets the current value of the property as a Class.
     *
     * @param defaultValue the value to return if the property is not defined,
     *    or is not the name of a Class
     * @return the current property value, or the default value if there is
     *    none or the property is not of the proper format
     */
    public Class<?> getNamedClass(Class<?> defaultValue) {
        return classValue.getValue(defaultValue);
    }

    @SuppressWarnings("unchecked")
    public <T> Optional<T> getCachedValue(Class<T> objectType) {
        T result = null;
        try {
            if (Integer.class.equals(objectType)) {
                result = (T) integerValue.getValue();
            } else if (String.class.equals(objectType)) {
                result = (T) cachedStringValue.getValue();
            } else if (Boolean.class.equals(objectType)) {
                result = (T) booleanValue.getValue();
            } else if (Float.class.equals(objectType)) {
                result = (T) floatValue.getValue();
            } else if (Double.class.equals(objectType)) {
                result = (T) doubleValue.getValue();
            } else if (Long.class.equals(objectType)) {
                result = (T) longValue.getValue();
            } else if (Class.class.equals(objectType)) {
                result = (T) classValue.getValue();
            }
        } catch (Exception e) {
            logger.warn("Type conversion error", e);
        }
        if (result == null) {
            return Optional.absent();
        } else {
            return Optional.of(result);
        }
    }

    /*
     * Updating the cached value
     */

    /**
     * Adds a callback to the DynamicProperty to run
     * when the value of the propety is updated.
     */
    public void addCallback(Runnable r) {
        if (r == null) {
            throw new NullPointerException("Cannot add null callback to DynamicProperty");
        }
        callbacks.add(r);
    }

    public void addValidator(PropertyChangeValidator validator) {
        if (validator == null) {
            throw new NullPointerException("Cannot add null validator to DynamicProperty");
        }
        validators.add(validator);
    }

    /**
     * Removes a callback to the DynamicProperty so that it will
     * no longer be run when the value of the propety is updated.
     *
     * @return true iff the callback was previously registered
     */
    public boolean removeCallback(Runnable r) {
        return callbacks.remove(r);
    }

    Set<Runnable> getCallbacks() {
        return callbacks;
    }

    private void notifyCallbacks() {
        for (Runnable r : callbacks) {
            try {
                r.run();
            } catch (Exception e) {
                logger.error("Error in DynamicProperty callback", e);
            }
        }
    }

    private void validate(String newValue) {
        for (PropertyChangeValidator v: validators) {
            try {
                v.validate(newValue);
            } catch (ValidationException e) {
                throw e;
            } catch (Throwable e) {
                throw new ValidationException("Unexpected exception during validation", e);
            }
        }
    }

    // return true iff the value actually changed
    private boolean updateValue() {
        String newValue;
        try {
            if (dynamicPropertySupportImpl != null) {
                newValue = dynamicPropertySupportImpl.getString(propName);
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("Unable to update property: " + propName, e);
            return false;
        }
        return updateValue(newValue);
    }

    private void setStatusForValues() {
        cachedStringValue.isCached = false;
        booleanValue.isCached = false;
        integerValue.isCached = false;
        floatValue.isCached = false;
        classValue.isCached = false;
        doubleValue.isCached = false;
        longValue.isCached = false;
    }

    // return true iff the value actually changed
    boolean updateValue(Object newValue) {
        String nv = (newValue == null) ? null : newValue.toString();
        synchronized (lock) {
            if ((nv == null && stringValue == null)
               || (nv != null && nv.equals(stringValue))) {
                return false;
            }
            stringValue = nv;
            setStatusForValues();
            changedTime = System.currentTimeMillis();
            return true;
        }
    }

    // return true iff the value actually changed
    private static boolean updateProperty(String propName, Object value) {
        DynamicProperty prop = ALL_PROPS.get(propName);
        if (prop != null && prop.updateValue(value)) {
            prop.notifyCallbacks();
            return true;
        }
        return false;
    }

    // return true iff _some_ value actually changed
    private static boolean updateAllProperties() {
        boolean changed = false;
        for (DynamicProperty prop : ALL_PROPS.values()) {
            if (prop.updateValue()) {
                prop.notifyCallbacks();
                changed = true;
            }
        }
        return changed;
    }

    private static void validate(String propName, Object value) {
        DynamicProperty prop = ALL_PROPS.get(propName);
        if (prop != null) {
            String newValue = (value == null)? null : value.toString();
            prop.validate(newValue);
        }
    }

    /**
     * A callback object that listens for configuration changes
     * and maintains cached property values.
     */
    static class DynamicPropertyListener implements PropertyListener {
        DynamicPropertyListener() { }
        @Override
        public void configSourceLoaded(Object source) {
            updateAllProperties();
        }
        @Override
        public void addProperty(Object source, String name, Object value, boolean beforeUpdate) {
            if (!beforeUpdate) {
                updateProperty(name, value);
            } else {
                validate(name, value);
            }
        }
        @Override
        public void setProperty(Object source, String name, Object value, boolean beforeUpdate) {
            if (!beforeUpdate) {
                updateProperty(name, value);
            } else {
                validate(name, value);
            }
        }
        @Override
        public void clearProperty(Object source, String name, Object value, boolean beforeUpdate) {
            if (!beforeUpdate) {
                updateProperty(name, value);
            }
        }
        @Override
        public void clear(Object source, boolean beforeUpdate) {
            if (!beforeUpdate) {
                updateAllProperties();
            }
        }
    }

    /**
     * Initialize the DynamicProperty capability by installing
     * a configuration listener.
     */
    static synchronized void initialize(DynamicPropertySupport config) {
        dynamicPropertySupportImpl = config;
        config.addConfigurationListener(new DynamicPropertyListener());
        updateAllProperties();
    }

    static void registerWithDynamicPropertySupport(DynamicPropertySupport config) {
        initialize(config);
    }

    /*
     * Object protocol
     */

    @Override
    public String toString() {
        return "{DynamicProperty:" + propName + "}";
    }

}


/**
 * Copyright 2014 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.sources;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughputExceededException;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.ScanResult;
import com.netflix.config.DynamicLongProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
 * User: gorzell
 * Date: 1/17/13
 * Time: 10:18 AM
 * Some of the basic plumbing and properties for a polling source that talks to Dynamo.
 */
public abstract class AbstractDynamoDbConfigurationSource <T> {
    private static final Logger log = LoggerFactory.getLogger(AbstractDynamoDbConfigurationSource.class);

    //Property names
    static final String tablePropertyName = "com.netflix.config.dynamo.tableName";
    static final String keyAttributePropertyName = "com.netflix.config.dynamo.keyAttributeName";
    static final String valueAttributePropertyName = "com.netflix.config.dynamo.valueAttributeName";
    static final String endpointPropertyName = "com.netflix.config.dynamo.endpoint";
    static final String pollingMaxBackOffMsPropertyName = "com.netflix.config.dynamo.maxPollingBackOffMs";
    static final String pollingMinBackOffMsPropertyName = "com.netflix.config.dynamo.maxPollingBackOffMs";
    static final String maxRetryCountPropertyName = "com.netflix.config.dynamo.maxRetryCount";

    //Property defaults
    static final String defaultTable = "archaiusProperties";
    static final String defaultKeyAttribute = "key";
    static final String defaultValueAttribute = "value";
    static final String defaultEndpoint = "dynamodb.us-east-1.amazonaws.com";
    static final Long defaultMaxBackOffMs = 5 * 1000L;
    static final Long defaultMinBackOffMs = 500L;
    static final Long defaultMaxRetryCount = 100L;

    //Dynamic Properties
    protected DynamicStringProperty tableName = DynamicPropertyFactory.getInstance()
            .getStringProperty(tablePropertyName, defaultTable);
    protected DynamicStringProperty keyAttributeName = DynamicPropertyFactory.getInstance()
            .getStringProperty(keyAttributePropertyName, defaultKeyAttribute);
    protected DynamicStringProperty valueAttributeName = DynamicPropertyFactory.getInstance()
            .getStringProperty(valueAttributePropertyName, defaultValueAttribute);
    protected DynamicStringProperty endpointName = DynamicPropertyFactory.getInstance()
            .getStringProperty(endpointPropertyName, defaultEndpoint);
    protected DynamicLongProperty maxBackOffMs = DynamicPropertyFactory.getInstance()
            .getLongProperty(pollingMaxBackOffMsPropertyName, defaultMaxBackOffMs);
    protected DynamicLongProperty minBackOffMs = DynamicPropertyFactory.getInstance()
            .getLongProperty(pollingMinBackOffMsPropertyName, defaultMinBackOffMs);
    protected DynamicLongProperty maxRetryCount = DynamicPropertyFactory.getInstance()
            .getLongProperty(maxRetryCountPropertyName, defaultMaxRetryCount);

    protected AmazonDynamoDB dbClient;

    public AbstractDynamoDbConfigurationSource() {
        this(new AmazonDynamoDBClient());
        setEndpoint();
    }

    public AbstractDynamoDbConfigurationSource(ClientConfiguration clientConfiguration) {
        this(new AmazonDynamoDBClient(clientConfiguration));
        setEndpoint();
    }

    public AbstractDynamoDbConfigurationSource(AWSCredentials credentials) {
        this(new AmazonDynamoDBClient(credentials));
        setEndpoint();
    }

    public AbstractDynamoDbConfigurationSource(AWSCredentials credentials, ClientConfiguration clientConfiguration) {
        this(new AmazonDynamoDBClient(credentials, clientConfiguration));
        setEndpoint();
    }

    public AbstractDynamoDbConfigurationSource(AWSCredentialsProvider credentialsProvider) {
        this(new AmazonDynamoDBClient(credentialsProvider));
        setEndpoint();
    }

    public AbstractDynamoDbConfigurationSource(AWSCredentialsProvider credentialsProvider, ClientConfiguration clientConfiguration) {
        this(new AmazonDynamoDBClient(credentialsProvider, clientConfiguration));
        setEndpoint();
    }

    public AbstractDynamoDbConfigurationSource(AmazonDynamoDB dbClient) {
        this.dbClient = dbClient;
    }

    protected ScanResult dbScanWithThroughputBackOff(ScanRequest scanRequest) {
        Long currentBackOffMs = minBackOffMs.get();
        Long retryCount = 0L;
        while (true) {
            try {
                return dbClient.scan(scanRequest);
            }
            catch (ProvisionedThroughputExceededException e) {
                currentBackOffMs = Math.min(currentBackOffMs * 2, maxBackOffMs.get());
                log.error(String.format("Failed to poll Dynamo due to ProvisionedThroughputExceededException. Backing off for %d ms.", currentBackOffMs));
                if (retryCount > maxRetryCount.get()) {
                  throw e;
                }
                retryCount++;

                try {
                    Thread.sleep(currentBackOffMs);
                } catch (InterruptedException ex) {
                }
            }
        }
    }

    protected abstract Map<String, T> loadPropertiesFromTable(String table);

    //TODO Javadoc
    public void validateDb(){
        String table = tableName.get();

        loadPropertiesFromTable(table);
        log.info("Successfully polled Dynamo for a new configuration based on table:" + table);
    }

    private void setEndpoint() {
        String endpoint = endpointName.get();
        dbClient.setEndpoint(endpoint);
        log.info("Set Dynamo endpoint:" + endpoint);
    }
}


/**
 * Copyright 2014 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.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.config.validation.PropertyChangeValidator;
import com.netflix.config.validation.ValidationException;

/**
 * A wrapper around DynamicProperty and associates it with a type.
 *
 * @param <V> The type of the DynamicProperty
 * @author awang
 */
public abstract class PropertyWrapper<V> implements Property<V> {
    protected final DynamicProperty prop;
    protected final V defaultValue;
    @SuppressWarnings("rawtypes")
    private static final IdentityHashMap<Class<? extends PropertyWrapper>, Object> SUBCLASSES_WITH_NO_CALLBACK
            = new IdentityHashMap<Class<? extends PropertyWrapper>, Object>();
    private static final Logger logger = LoggerFactory.getLogger(PropertyWrapper.class);
    private final Set<Runnable> callbacks = new CopyOnWriteArraySet<Runnable>();

    static {
        PropertyWrapper.registerSubClassWithNoCallback(DynamicIntProperty.class);
        PropertyWrapper.registerSubClassWithNoCallback(DynamicStringProperty.class);
        PropertyWrapper.registerSubClassWithNoCallback(DynamicBooleanProperty.class);
        PropertyWrapper.registerSubClassWithNoCallback(DynamicFloatProperty.class);
        PropertyWrapper.registerSubClassWithNoCallback(DynamicLongProperty.class);
        PropertyWrapper.registerSubClassWithNoCallback(DynamicDoubleProperty.class);
    }

    private static final Object DUMMY_VALUE = new Object();

    /**
     * By default, a subclass of PropertyWrapper will automatically register {@link #propertyChanged()} as a callback
     * for property value change. This method provide a way for a subclass to avoid this overhead if it is not interested
     * to get callback.
     *
     * @param c
     */
    public static final void registerSubClassWithNoCallback(@SuppressWarnings("rawtypes") Class<? extends PropertyWrapper> c) {
        SUBCLASSES_WITH_NO_CALLBACK.put(c, DUMMY_VALUE);
    }

    protected PropertyWrapper(String propName, V defaultValue) {
        this.prop = DynamicProperty.getInstance(propName);
        this.defaultValue = defaultValue;
        Class<?> c = getClass();
        // this checks whether this constructor is called by a class that
        // extends the immediate sub classes (IntProperty, etc.) of PropertyWrapper.
        // If yes, it is very likely that propertyChanged()  is overriden
        // in the sub class and we need to register the callback.
        // Otherwise, we know that propertyChanged() does nothing in
        // immediate subclasses and we can avoid registering the callback, which
        // has the cost of modifying the CopyOnWriteArraySet
        if (!SUBCLASSES_WITH_NO_CALLBACK.containsKey(c)) {
            Runnable callback = new Runnable() {
                public void run() {
                    propertyChanged();
                }
            };
            this.prop.addCallback(callback);
            callbacks.add(callback);
            this.prop.addValidator(new PropertyChangeValidator() {
                @Override
                public void validate(String newValue) {
                    PropertyWrapper.this.validate(newValue);
                }
            });
            try {
                if (this.prop.getString() != null) {
                    this.validate(this.prop.getString());
                }
            } catch (ValidationException e) {
                logger.warn("Error validating property at initialization. Will fallback to default value.", e);
                prop.updateValue(defaultValue);
            }
        }
    }

    @Override
    public String getName() {
        return prop.getName();
    }

    public void addValidator(PropertyChangeValidator v) {
        if (v != null) {
            prop.addValidator(v);
        }
    }

    /**
     * Called when the property value is updated.
     * The default does nothing.
     * Subclasses are free to override this if desired.
     */
    protected void propertyChanged() {
        propertyChanged(getValue());
    }

    /**
     * Called when the property value is updated.
     * The default does nothing.
     * Subclasses are free to override this if desired.
     */
    protected void propertyChanged(V newValue) {
        // by default, do nothing
    }

    protected void validate(String newValue) {
    }

    /**
     * Gets the time (in milliseconds past the epoch) when the property
     * was last set/changed.
     */
    @Override
    public long getChangedTimestamp() {
        return prop.getChangedTimestamp();
    }

    /**
     * Add the callback to be triggered when the value of the property is changed
     *
     * @param callback
     */
    @Override
    public void addCallback(Runnable callback) {
        if (callback != null) {
            prop.addCallback(callback);
            callbacks.add(callback);
        }
    }

    /**
     * Remove all callbacks registered through this instance of property
     */
    @Override
    public void removeAllCallbacks() {
        final Set<Runnable> callbacksToRemove = new HashSet<Runnable>(callbacks);
        for (Runnable callback : callbacksToRemove) {
            prop.removeCallback(callback);
        }
        callbacks.removeAll(callbacksToRemove);
    }

    /**
     * Get current typed value of the property.
     */
    @Override
    public abstract V getValue();

    @Override
    public V getDefaultValue() {
        return defaultValue;
    }

    public DynamicProperty getDynamicProperty() {
        return prop;
    }

    @Override
    public String toString() {
        return "DynamicProperty: {name=" + prop.getName() + ", current value="
                + prop.getString(String.valueOf(defaultValue)) + "}";
    }
}


/**
 * Copyright 2014 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.util;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

/**
 * There are a few features in platform and allied libraries which need to parse
 * a property of the form <uriRegex> OR <HTTP Verb
 * name><space><uriRegex>. This class provides the functionality
 * to represent such a property value including static methods to parse such
 * property values.
 *
 * @author pkamath
 *
 */
public class HttpVerbUriRegexPropertyValue {

    private static final String METHOD_SEPARATOR = " ";
    private Verb verb = Verb.ANY_VERB;
    private String uriRegex = null;

    public HttpVerbUriRegexPropertyValue(Verb verb, String uriRegex) {
        this.verb = verb;
        this.uriRegex = uriRegex;
    }

    /**
     * Expects property value to be of the form <uriRegex> OR <HTTP
     * Verb name><space><uriRegex>
     *
     * @param propValue
     *            property value
     * @return {@link HttpVerbUriRegexPropertyValue} instance corresponding to
     *         propValue if propValue is not null, null otherwise
     */
    public static HttpVerbUriRegexPropertyValue getVerbUriRegex(String propValue) {
        HttpVerbUriRegexPropertyValue returnValue = null;
        if (propValue != null) {
            propValue = propValue.trim();
            int methodSeparatorIndex = propValue.indexOf(METHOD_SEPARATOR);
            String uriRegex = propValue; // to begin with
            Verb verb = Verb.ANY_VERB;
            if (methodSeparatorIndex != -1) {
                // user may have supplied a verb
                verb = getVerb(propValue.substring(0, methodSeparatorIndex));
                if (verb != Verb.ANY_VERB) {
                    // update uriRegex
                    uriRegex = propValue.substring(methodSeparatorIndex + 1);
                }
            }
            returnValue = new HttpVerbUriRegexPropertyValue(verb, uriRegex);
        }
        return returnValue;
    }

    private static Verb getVerb(String verbName) {
        if (verbName != null) {
            Verb v = Verb.get(verbName.trim().toUpperCase());
            return v == null ? Verb.ANY_VERB : v;
        }
        return Verb.ANY_VERB;
    }

    public Verb getVerb() {
        return verb;
    }

    public String getUriRegex() {
        return uriRegex;
    }

    public enum Verb {
        GET("GET"), PUT("PUT"), POST("POST"), DELETE("DELETE"), OPTIONS(
                "OPTIONS"), HEAD("HEAD"), ANY_VERB("ANY"); // catch-all -
                                                           // essentially any
                                                           // verb!

        private final String verb; // http method
        private static final Map<String, Verb> lookup = new HashMap<String, Verb>();

        static {
            for (Verb v : EnumSet.allOf(Verb.class))
                lookup.put(v.name(), v);
        }

        private Verb(String verb) {
            this.verb = verb;
        }

        public String toString() {
            return verb;
        }

        public static Verb get(String verbName) {
            return lookup.get(verbName.toUpperCase());
        }
    }

    public static String getMethodSeparator() {
        return METHOD_SEPARATOR;
    }
}