Skip to content

Commit

Permalink
Merge pull request #116 from pith/refactor-bootstrap-config
Browse files Browse the repository at this point in the history
Fix #113. Add more features to the bootstrap props
  • Loading branch information
pith committed Sep 11, 2015
2 parents e677a5b + 7bb15ae commit 76ed4da
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
*/
package org.seedstack.seed.core.internal;

import com.google.common.collect.ImmutableMap;
import com.google.inject.Module;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.PluginException;
Expand All @@ -18,16 +17,14 @@
import io.nuun.kernel.core.AbstractPlugin;
import io.nuun.kernel.core.internal.scanner.AbstractClasspathScanner;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.MapConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.StringUtils;
import org.reflections.vfs.Vfs;
import org.seedstack.seed.core.api.CoreErrorCode;
import org.seedstack.seed.core.api.DiagnosticManager;
import org.seedstack.seed.core.api.Install;
import org.seedstack.seed.core.api.SeedException;
import org.seedstack.seed.core.internal.scan.ClasspathScanHandler;
import org.seedstack.seed.core.internal.scan.FallbackUrlType;
import org.seedstack.seed.core.api.CoreErrorCode;
import org.seedstack.seed.core.spi.diagnostic.DiagnosticDomain;
import org.seedstack.seed.core.spi.diagnostic.DiagnosticInfoCollector;
import org.seedstack.seed.core.utils.SeedReflectionUtils;
Expand All @@ -49,8 +46,6 @@ public class CorePlugin extends AbstractPlugin {
public static final String SEED_PACKAGE_ROOT = "org.seedstack";
public static final String CORE_PLUGIN_PREFIX = "org.seedstack.seed.core";
public static final String DETAILS_MESSAGE = "Details of the previous error below";

private static final String SEED_BOOTSTRAP_PATH = "META-INF/seed-bootstrap.properties";
private static final Logger LOGGER = LoggerFactory.getLogger(CorePlugin.class);

private static final DiagnosticManagerImpl DIAGNOSTIC_MANAGER = new DiagnosticManagerImpl();
Expand Down Expand Up @@ -102,7 +97,7 @@ public static DiagnosticManager getDiagnosticManager() {
}

public CorePlugin() {
bootstrapConfiguration = loadBootstrapConfiguration();
bootstrapConfiguration = new SeedConfigLoader().bootstrapConfig();
}

@Override
Expand Down Expand Up @@ -176,28 +171,6 @@ public String pluginPackageRoot() {
return packageRoots;
}

private Configuration loadBootstrapConfiguration() {
MapConfiguration globalConfiguration = new MapConfiguration(new HashMap<String, Object>());

ClassLoader classLoader = SeedReflectionUtils.findMostCompleteClassLoader();
if (classLoader == null) {
throw SeedException.createNew(CoreErrorCode.UNABLE_TO_FIND_CLASSLOADER);
}

try {
Enumeration<URL> urls = classLoader.getResources(SEED_BOOTSTRAP_PATH);

while (urls.hasMoreElements()) {
URL url = urls.nextElement();
globalConfiguration.append(new PropertiesConfiguration(url));
}
} catch (Exception e) {
throw SeedException.wrap(e, CoreErrorCode.UNEXPECTED_EXCEPTION);
}

return new MapConfiguration(new ImmutableMap.Builder<String, Object>().putAll(globalConfiguration.getMap()).build());
}

/**
* Returns the configuration coming from the SEED bootstrap properties.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/**
* 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 { // NOSONAR
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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*
* @author adrien.lauer@mpsa.com
*/
enum ApplicationErrorCode implements ErrorCode {
public enum ApplicationErrorCode implements ErrorCode {
MISSING_APPLICATION_IDENTIFIER,
STORAGE_PATH_IS_NOT_A_DIRECTORY,
UNABLE_TO_CREATE_STORAGE_DIRECTORY,
Expand Down

0 comments on commit 76ed4da

Please sign in to comment.