All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.archaius.config.DefaultCompositeConfig Maven / Gradle / Ivy

/**
 * Copyright 2015 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.archaius.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;

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

import com.netflix.archaius.api.Config;
import com.netflix.archaius.api.ConfigListener;
import com.netflix.archaius.api.exceptions.ConfigException;

/**
 * Config that is a composite of multiple configuration and as such doesn't track 
 * properties of its own.  The composite does not merge the configurations but instead
 * treats them as overrides so that a property existing in a configuration supersedes
 * the same property in configuration that was added later.  It is however possible
 * to set a flag that reverses the override order.
 * 
 * @author elandau
 *
 * TODO: Optional cache of queried properties
 * TODO: Resolve method to collapse all the child configurations into a single config
 * TODO: Combine children and lookup into a single LinkedHashMap
 */
public class DefaultCompositeConfig extends AbstractConfig implements com.netflix.archaius.api.config.CompositeConfig {
    private static final Logger LOG = LoggerFactory.getLogger(DefaultCompositeConfig.class);
    
    /**
     * The builder provides a fluent style API to create a CompositeConfig
     * @author elandau
     */
    public static class Builder {
        LinkedHashMap configs = new LinkedHashMap<>();
        
        public Builder withConfig(String name, Config config) {
            configs.put(name, config);
            return this;
        }
        
        public com.netflix.archaius.api.config.CompositeConfig build() throws ConfigException {
            com.netflix.archaius.api.config.CompositeConfig config = new DefaultCompositeConfig();
            for (Entry entry : configs.entrySet()) {
                config.addConfig(entry.getKey(), entry.getValue());
            }
            return config;
        }
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static com.netflix.archaius.api.config.CompositeConfig create() throws ConfigException {
        return DefaultCompositeConfig.builder().build();
    }
    
    private final CopyOnWriteArrayList  children  = new CopyOnWriteArrayList();
    private final Map           lookup    = new LinkedHashMap();
    private final ConfigListener                listener;
    private final boolean                       reversed;
    
    public DefaultCompositeConfig() {
        this(false);
    }
    
    public DefaultCompositeConfig(boolean reversed) {
        this.reversed = reversed;
        listener = new ConfigListener() {
            @Override
            public void onConfigAdded(Config config) {
                notifyConfigAdded(DefaultCompositeConfig.this);
            }

            @Override
            public void onConfigRemoved(Config config) {
                notifyConfigRemoved(DefaultCompositeConfig.this);
            }

            @Override
            public void onConfigUpdated(Config config) {
                notifyConfigUpdated(DefaultCompositeConfig.this);
            }

            @Override
            public void onError(Throwable error, Config config) {
                notifyError(error, DefaultCompositeConfig.this);
            }
        };
    }

    @Override
    public synchronized boolean addConfig(String name, Config child) throws ConfigException {
        return internalAddConfig(name, child);
    }
    
    private synchronized boolean internalAddConfig(String name, Config child) throws ConfigException {
        LOG.trace("Adding config {} to {}", name, hashCode());
        
        if (child == null) {
            // TODO: Log a warning?
            return false;
        }
        
        if (name == null) {
            throw new ConfigException("Child configuration must be named");
        }
        
        if (lookup.containsKey(name)) {
            LOG.info("Configuration with name'{}' already exists", name);
            return false;
        }

        lookup.put(name, child);
        if (reversed) {
            children.add(0, child);
        }
        else {
            children.add(child);
        }
        child.addListener(listener);
        postConfigAdded(child);
        return true;
    }

    @Override
    public synchronized void addConfigs(LinkedHashMap configs) throws ConfigException {
        for (Entry entry : configs.entrySet()) {
            internalAddConfig(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public void replaceConfigs(LinkedHashMap configs) throws ConfigException {
        for (Entry entry : configs.entrySet()) {
            replaceConfig(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public synchronized Collection getConfigNames() {
        List result = new ArrayList();
        result.addAll(this.lookup.keySet());
        return result;
    }
    
    protected void postConfigAdded(Config child) {
        child.setStrInterpolator(getStrInterpolator());
        child.setDecoder(getDecoder());
        notifyConfigAdded(child);
        child.addListener(listener);
    }

    @Override
    public synchronized void replaceConfig(String name, Config child) throws ConfigException {
        internalRemoveConfig(name);
        internalAddConfig(name, child);
    }

    @Override
    public synchronized Config removeConfig(String name) {
        return internalRemoveConfig(name);
    }
    
    public synchronized Config internalRemoveConfig(String name) {
        Config child = this.lookup.remove(name);
        if (child != null) {
            this.children.remove(child);
            child.removeListener(listener);
            this.notifyConfigRemoved(child);
            return child;
        }
        return null;
    }    

    @Override
    public Config getConfig(String name) {
        return lookup.get(name);
    }


    @Override
    public Object getRawProperty(String key) {
        for (Config child : children) {
            if (child.containsKey(key)) {
                return child.getRawProperty(key);
            }
        }
        return null;
    }

    @Override
    public  List getList(String key, Class type) {
        for (Config child : children) {
            if (child.containsKey(key)) {
                return child.getList(key, type);
            }
        }
        return notFound(key);
    }

    @Override
    public List getList(String key) {
        for (Config child : children) {
            if (child.containsKey(key)) {
                return child.getList(key);
            }
        }
        return notFound(key);
    }

    @Override
    public boolean containsKey(String key) {
        for (Config child : children) {
            if (child.containsKey(key)) {
                return true;
            }
        }
        
        return false;
    }

    @Override
    public boolean isEmpty() {
        for (Config child : children) {
            if (!child.isEmpty()) {
                return false;
            }
        }
        
        return true;
    }

    /**
     * Return a set of all unique keys tracked by any child of this composite.
     * This can be an expensive operations as it requires iterating through all of
     * the children.
     * 
     * TODO: Cache keys
     */
    @Override
    public Iterator getKeys() {
        HashSet result = new HashSet<>();
        for (Config config : children) {
            Iterator iter = config.getKeys();
            while (iter.hasNext()) {
               String key = iter.next();
                result.add(key);
            }
        }
        return result.iterator();
    }
    
    @Override
    public synchronized  T accept(Visitor visitor) {
        T result = null;
        if (visitor instanceof CompositeVisitor) {
            synchronized (this) {
                CompositeVisitor cv = (CompositeVisitor)visitor;
                for (Entry entry : lookup.entrySet()) {
                    result = cv.visitChild(entry.getKey(), entry.getValue());
                }
            }
        }
        else {
            for (Config child : children) {
                result = child.accept(visitor);
            }
        }
        return result;
    }

    public static com.netflix.archaius.api.config.CompositeConfig from(LinkedHashMap load) throws ConfigException {
        Builder builder = builder();
        for (Entry config : load.entrySet()) {
            builder.withConfig(config.getKey(), config.getValue());
        }
        return builder.build();
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        
        for (Config child : children) {
            sb.append(child.toString()).append(" ");
        }
        sb.append("]");
        return sb.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy