/* * Copyright 2014 Avanza Bank AB * * 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.avanza.astrix.config; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; /** * This is an abstraction for a hierarchical set of configuration sources. Each property is resolved * by querying each ConfigurationSource in turn until the property is resolved. <p> * * Each {@link DynamicProperty} read is cached in the {@link DynamicConfig} instance. The first time a property * with a given name is read, an instance of the given {@link DynamicProperty} type is created, and its value is * bound to the underlying configuration sources. * * @author Elias Lindholm (elilin) * */ public final class DynamicConfig { private final ObjectCache configCache = new ObjectCache(); private final List<DynamicConfigSource> configSources; private final ListenerSupport<DynamicConfigListener> dynamicConfigListenerSupport = new ListenerSupport<>(); public DynamicConfig(ConfigSource configSource) { this(Arrays.asList(configSource)); } /** * Creates a {@link DynamicConfig} instance resolving configuration properties using * the defined set of {@link ConfigSource}'s (possibly {@link DynamicConfigSource}). <p> * * @param first * @param other * @return */ public static DynamicConfig create(ConfigSource first, ConfigSource... other) { List<ConfigSource> sources = new LinkedList<>(); sources.add(first); sources.addAll(Arrays.asList(other)); return new DynamicConfig(sources); } public static DynamicConfig create(List<? extends ConfigSource> sources) { return new DynamicConfig(sources); } public DynamicConfig(List<? extends ConfigSource> configSources) { this.configSources = new ArrayList<>(configSources.size()); for (ConfigSource configSource : configSources) { if (configSource instanceof DynamicConfigSource) { this.configSources.add(DynamicConfigSource.class.cast(configSource)); } else { this.configSources.add(new DynamicConfigSourceAdapter(configSource)); } } } private static class DynamicConfigSourceAdapter extends AbstractDynamicConfigSource { private final ConfigSource configSource; public DynamicConfigSourceAdapter(ConfigSource configSource) { this.configSource = configSource; } @Override public String get(String propertyName, DynamicPropertyListener<String> propertyChangeListener) { return configSource.get(propertyName); } @Override public String toString() { return this.configSource.toString(); } } /** * Reads a property of String type. * * @param name * @return */ public DynamicStringProperty getStringProperty(String name, String defaultValue) { return getProperty(name, DynamicStringProperty.class, defaultValue, PropertyParser.STRING_PARSER); } public DynamicBooleanProperty getBooleanProperty(String name, boolean defaultValue) { return getProperty(name, DynamicBooleanProperty.class, defaultValue, PropertyParser.BOOLEAN_PARSER); } public DynamicLongProperty getLongProperty(String name, long defaultValue) { return getProperty(name, DynamicLongProperty.class, defaultValue, PropertyParser.LONG_PARSER); } public DynamicIntProperty getIntProperty(String name, int defaultValue) { return getProperty(name, DynamicIntProperty.class, defaultValue, PropertyParser.INT_PARSER); } private <T, P extends DynamicProperty<T>> P getProperty(String name, Class<P> propertyType, T defaultValue, PropertyParser<T> propertyParser) { return this.configCache.getInstance(propertyType.getSimpleName() + "." + name, () -> bindPropertyToConfigurationSources(name, propertyType.newInstance(), defaultValue, propertyParser)); } private <T, P extends DynamicProperty<T>> P bindPropertyToConfigurationSources(String name, P property, T defaultValue, PropertyParser<T> propertyParser) { DynamicPropertyChain<T> chain = createPropertyChain(name, defaultValue, propertyParser); chain.bindTo(property::setValue); notifyPropertyCreated(name, property.getCurrentValue()); property.addListener(newValue -> notifyPropertyChanged(name, newValue)); return property; } private <T> void notifyPropertyCreated(String propertyName, T initialValue) { dynamicConfigListenerSupport.dispatchEvent(listener -> listener.propertyCreated(propertyName, initialValue)); } private <T> void notifyPropertyChanged(String propertyNAme, T newValue) { dynamicConfigListenerSupport.dispatchEvent(listener -> listener.propertyChanged(propertyNAme, newValue)); } private <T> DynamicPropertyChain<T> createPropertyChain(String name, T defaultValue, PropertyParser<T> propertyParser) { DynamicPropertyChain<T> chain = DynamicPropertyChain.createWithDefaultValue(defaultValue, propertyParser); for (DynamicConfigSource configSource : configSources) { DynamicConfigProperty<T> newValueInChain = chain.appendValue(); // bind newValueInChain to configuration property in source String propertyValue = configSource.get(name, newValueInChain); newValueInChain.set(propertyValue); } return chain; } public static DynamicConfig merged(DynamicConfig dynamicConfigA, DynamicConfig dynamicConfigB) { List<ConfigSource> merged = new ArrayList<>(dynamicConfigA.configSources.size() + dynamicConfigB.configSources.size()); merged.addAll(dynamicConfigA.configSources); merged.addAll(dynamicConfigB.configSources); return new DynamicConfig(merged); } @Override public String toString() { return this.configSources.toString(); } /** * Adds a listener to this DynamicConfig instance. * * The listener receives a "propertyChanged" event each time the resolved * value of a DynamicProperty read * from this instance changes. * * The listener receives a "propertyCreated" each time a new property * is created in this {@link DynamicConfig} instance (i.e the first time * a property with a given name is read). * * @param l */ public void addListener(DynamicConfigListener l) { this.dynamicConfigListenerSupport.addListener(l); } }