org.seedstack.seed.core.internal.SeedConfigLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.seedstack.seed.core.internal.SeedConfigLoader.java

Source

/**
 * Copyright (c) 2013-2015 by The SeedStack authors. All rights reserved.
 *
 * This file is part of SeedStack, An enterprise-oriented full development stack.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.seedstack.seed.core.internal;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import jodd.props.Props;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.MapConfiguration;
import org.javatuples.Pair;
import org.seedstack.seed.core.api.CoreErrorCode;
import org.seedstack.seed.core.api.SeedException;
import org.seedstack.seed.core.internal.application.ApplicationErrorCode;
import org.seedstack.seed.core.internal.application.EnvLookup;
import org.seedstack.seed.core.utils.SeedReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

/**
 * Utility class which allows to load the application and Seed properties.
 *
 * @author pierre.thirouin@ext.mpsa.com (Pierre Thirouin)
 */
public class SeedConfigLoader {

    private static final String SEED_BOOTSTRAP_PROPS_PATH = "META-INF/seed-bootstrap.props";
    private static final String SEED_BOOTSTRAP_PROPERTIES_PATH = "META-INF/seed-bootstrap.properties";
    private static final Logger LOGGER = LoggerFactory.getLogger(SeedConfigLoader.class);

    // Please let the volatile modifier on bootstrapConfiguration
    private static volatile Configuration bootstrapConfiguration;

    /**
     * Gets the configuration needed to bootstrap a Seed application.
     * <p>
     * This configuration is loaded from the seed-bootstrap.properties files.
     * If multiple files are present the configuration is concatenated.
     * </p>
     *
     * @return the bootstrap configuration
     */
    public Configuration bootstrapConfig() {
        if (bootstrapConfiguration == null) {
            synchronized (SeedConfigLoader.class) {
                if (bootstrapConfiguration == null) {
                    bootstrapConfiguration = buildBootstrapConfiguration();
                }
            }
        }
        return bootstrapConfiguration;
    }

    private Configuration buildBootstrapConfiguration() {
        Set<String> resources = Sets.newHashSet(SEED_BOOTSTRAP_PROPS_PATH, SEED_BOOTSTRAP_PROPERTIES_PATH);
        MapConfiguration globalConfiguration = buildConfiguration(resources, null).getValue0();
        globalConfiguration.getInterpolator().registerLookup("env", new EnvLookup());
        return new MapConfiguration(
                new ImmutableMap.Builder<String, Object>().putAll(globalConfiguration.getMap()).build());
    }

    /**
     * Gets the application active profiles.
     *
     * @return the array of active profiles or null if none is active
     */
    public String[] applicationProfiles() {
        return getStringArray(System.getProperty("org.seedstack.seed.profiles"));
    }

    /**
     * Build the application configuration.
     *
     * @param configurationResources the paths to the configuration resources
     * @param defaultConfiguration   the default configuration registered with the SPI
     * @return the final configuration
     */
    public Pair<MapConfiguration, Props> buildConfiguration(Set<String> configurationResources,
            @Nullable Map<String, String> defaultConfiguration) {
        final Props props = buildProps();
        final Props propsOverride = buildProps();

        for (String configurationResource : configurationResources) {
            try {
                ClassLoader classLoader = SeedReflectionUtils.findMostCompleteClassLoader();
                if (classLoader == null) {
                    throw SeedException.createNew(CoreErrorCode.UNABLE_TO_FIND_CLASSLOADER);
                }
                Enumeration<URL> urlEnumeration = classLoader.getResources(configurationResource);
                while (urlEnumeration.hasMoreElements()) {
                    URL url = urlEnumeration.nextElement();
                    InputStream resourceAsStream = null;

                    try {
                        resourceAsStream = url.openStream();

                        if (isOverrideResource(configurationResource)) {
                            LOGGER.debug("Adding {} to configuration override", url.toExternalForm());
                            propsOverride.load(resourceAsStream);
                        } else {
                            LOGGER.debug("Adding {} to configuration", url.toExternalForm());
                            props.load(resourceAsStream);
                        }
                    } finally {
                        if (resourceAsStream != null) {
                            try {
                                resourceAsStream.close();
                            } catch (IOException e) {
                                LOGGER.warn("Unable to close configuration resource " + configurationResource, e);
                            }
                        }
                    }
                }
            } catch (IOException e) {
                throw SeedException.wrap(e, ApplicationErrorCode.UNABLE_TO_LOAD_CONFIGURATION_RESOURCE)
                        .put("resource", configurationResource);
            }
        }

        // Build configuration
        return Pair.with(buildConfiguration(props, propsOverride, defaultConfiguration), props);
    }

    /**
     * Indicates whether the resource path represents an override file, i.e. all the files ending with
     * ".override.properties" or the files ".override.props".
     *
     * @param configurationResource the path to test
     * @return true if the path correspond to an override file, false otherwise.
     */
    public boolean isOverrideResource(String configurationResource) {
        return configurationResource.endsWith(".override.properties")
                || configurationResource.endsWith(".override.props");
    }

    private Props buildProps() {
        Props newProps = new Props();

        newProps.setSkipEmptyProps(false);
        newProps.setAppendDuplicateProps(true);

        return newProps;
    }

    private MapConfiguration buildConfiguration(Props props, Props propsOverride,
            Map<String, String> defaultConfiguration) {
        Map<String, String> finalConfiguration = new HashMap<String, String>();
        Map<String, String> configurationMap = new HashMap<String, String>();
        Map<String, String> configurationOverrideMap = new HashMap<String, String>();

        // Extract props to maps
        props.extractProps(configurationMap, applicationProfiles());
        propsOverride.extractProps(configurationOverrideMap, applicationProfiles());

        // Put defaults to final configuration
        if (defaultConfiguration != null) {
            finalConfiguration.putAll(defaultConfiguration);
        }

        // Put nominal to final configuration
        finalConfiguration.putAll(configurationMap);

        applyPropertiesRemoval(finalConfiguration, configurationOverrideMap);

        // Put override to final configuration
        finalConfiguration.putAll(configurationOverrideMap);

        // Convert final configuration to an immutable Apache Commons Configuration
        return new MapConfiguration(new ImmutableMap.Builder<String, Object>().putAll(finalConfiguration).build());
    }

    /**
     * Looks the override config for properties starting with "-". These properties will be removed from the actual config.
     *
     * For instance the config contains:
     * <pre>
     * key1=foo
     * </pre>
     * and the override config contains:
     * <pre>
     * -key1
     * </pre>
     * The property will be removed from the final configuration
     *
     * @param config         the actual configuration
     * @param overrideConfig the configuration containing the overrides
     */
    private void applyPropertiesRemoval(Map<String, String> config, Map<String, String> overrideConfig) {
        Iterator<Map.Entry<String, String>> overrideIterator = overrideConfig.entrySet().iterator();
        while (overrideIterator.hasNext()) {
            String overrideKey = overrideIterator.next().getKey();
            if (overrideKey.startsWith("-")) {
                config.remove(overrideKey.substring(1));
                overrideIterator.remove();
            }
        }
    }

    private String[] getStringArray(String value) {
        if (value == null) {
            return null;
        } else {
            String[] split = value.split(",");
            for (int i = 0; i < split.length; i++) {
                split[i] = split[i].trim();
            }

            if (split.length == 0) {
                return null;
            } else {
                return split;
            }
        }
    }
}