/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* 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.configuration;
import com.google.common.collect.Sets;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.context.InitContext;
import io.nuun.kernel.api.plugin.request.ClasspathScanRequest;
import io.nuun.kernel.core.AbstractPlugin;
import org.seedstack.coffig.Coffig;
import org.seedstack.coffig.provider.InMemoryProvider;
import org.seedstack.coffig.provider.JacksonProvider;
import org.seedstack.coffig.provider.PropertiesProvider;
import org.seedstack.seed.Application;
import org.seedstack.seed.ApplicationConfig;
import org.seedstack.seed.SeedException;
import org.seedstack.seed.core.SeedRuntime;
import org.seedstack.seed.core.internal.CoreErrorCode;
import org.seedstack.seed.diagnostic.DiagnosticManager;
import org.seedstack.seed.spi.ApplicationProvider;
import org.seedstack.shed.ClassLoaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Core plugins that detects configuration files and adds them to the global configuration object.
*/
public class ConfigurationPlugin extends AbstractPlugin implements ApplicationProvider {
public static final String NAME = "config";
public static final String EXTERNAL_CONFIG_PREFIX = "seedstack.config.";
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationPlugin.class);
private static final String CONFIGURATION_PACKAGE = "META-INF.configuration";
private static final String CONFIGURATION_LOCATION = "META-INF/configuration/";
private static final String YAML_REGEX = ".*\\.yaml";
private static final String YML_REGEX = ".*\\.yml";
private static final String JSON_REGEX = ".*\\.json";
private static final String PROPERTIES_REGEX = ".*\\.properties";
private SeedRuntime seedRuntime;
private Coffig coffig;
private DiagnosticManager diagnosticManager;
private ApplicationConfig applicationConfig;
private Application application;
@Override
public String name() {
return NAME;
}
@Override
public void provideContainerContext(Object containerContext) {
seedRuntime = (SeedRuntime) containerContext;
coffig = seedRuntime.getConfiguration();
diagnosticManager = seedRuntime.getDiagnosticManager();
applicationConfig = seedRuntime.getApplicationConfig();
}
@Override
public String pluginPackageRoot() {
if (applicationConfig.getBasePackages().isEmpty() && applicationConfig.isPackageScanWarning()) {
LOGGER.warn("No base package configured, only classes in 'org.seedstack.*' packages will be scanned");
}
Set<String> basePackages = new HashSet<>(applicationConfig.getBasePackages());
basePackages.add(CONFIGURATION_PACKAGE);
return String.join(",", basePackages);
}
@Override
public Collection<ClasspathScanRequest> classpathScanRequests() {
return classpathScanRequestBuilder()
.resourcesRegex(YAML_REGEX)
.resourcesRegex(YML_REGEX)
.resourcesRegex(JSON_REGEX)
.resourcesRegex(PROPERTIES_REGEX)
.build();
}
@SuppressWarnings("unchecked")
@Override
public InitState init(InitContext initContext) {
detectSystemPropertiesConfig();
detectKernelParamConfig(initContext);
detectConfigurationFiles(initContext);
application = new ApplicationImpl(coffig, applicationConfig);
diagnosticManager.registerDiagnosticInfoCollector("application", new ApplicationDiagnosticCollector(application));
return InitState.INITIALIZED;
}
private void detectKernelParamConfig(InitContext initContext) {
InMemoryProvider kernelParamConfigProvider = new InMemoryProvider();
for (Map.Entry<String, String> kernelParam : initContext.kernelParams().entrySet()) {
if (kernelParam.getKey().startsWith(EXTERNAL_CONFIG_PREFIX)) {
addValue(kernelParamConfigProvider, kernelParam.getKey(), kernelParam.getValue());
}
}
seedRuntime.registerConfigurationProvider(
kernelParamConfigProvider,
ConfigurationPriority.KERNEL_PARAMETERS_CONFIG
);
}
private void addValue(InMemoryProvider inMemoryProvider, String key, String value) {
String choppedKey = key.substring(EXTERNAL_CONFIG_PREFIX.length());
if (value.contains(",")) {
String[] values = Arrays.stream(value.split(",")).map(String::trim).toArray(String[]::new);
LOGGER.debug("External array configuration property: {}={}", choppedKey, Arrays.toString(values));
inMemoryProvider.put(choppedKey, values);
} else {
LOGGER.debug("External configuration property: {}={}", choppedKey, value);
inMemoryProvider.put(choppedKey, value);
}
}
private void detectSystemPropertiesConfig() {
InMemoryProvider systemPropertiesProvider = new InMemoryProvider();
Properties systemProperties = System.getProperties();
for (String systemProperty : systemProperties.stringPropertyNames()) {
if (systemProperty.startsWith(EXTERNAL_CONFIG_PREFIX)) {
addValue(systemPropertiesProvider, systemProperty, systemProperties.getProperty(systemProperty));
}
}
seedRuntime.registerConfigurationProvider(
systemPropertiesProvider,
ConfigurationPriority.SYSTEM_PROPERTIES_CONFIG
);
}
private void detectConfigurationFiles(InitContext initContext) {
JacksonProvider jacksonProvider = new JacksonProvider();
JacksonProvider jacksonOverrideProvider = new JacksonProvider();
PropertiesProvider propertiesProvider = new PropertiesProvider();
PropertiesProvider propertiesOverrideProvider = new PropertiesProvider();
for (String configurationResource : retrieveConfigurationResources(initContext)) {
try {
ClassLoader classLoader = ClassLoaders.findMostCompleteClassLoader();
Enumeration<URL> urlEnumeration = classLoader.getResources(configurationResource);
while (urlEnumeration.hasMoreElements()) {
URL url = urlEnumeration.nextElement();
if (isOverrideResource(configurationResource)) {
LOGGER.debug("Detected override configuration resource: {}", url.toExternalForm());
if (isJacksonResource(configurationResource)) {
jacksonOverrideProvider.addSource(url);
} else if (isPropertiesResource(configurationResource)) {
propertiesOverrideProvider.addSource(url);
} else {
LOGGER.warn("Unrecognized override configuration resource: {}", url.toExternalForm());
}
} else {
LOGGER.debug("Detected configuration resource: {}", url.toExternalForm());
if (isJacksonResource(configurationResource)) {
jacksonProvider.addSource(url);
} else if (isPropertiesResource(configurationResource)) {
propertiesProvider.addSource(url);
} else {
LOGGER.warn("Unrecognized configuration resource: {}", url.toExternalForm());
}
}
}
} catch (IOException e) {
throw SeedException.wrap(e, CoreErrorCode.UNABLE_TO_LOAD_CONFIGURATION_RESOURCE).put("resource", configurationResource);
}
}
seedRuntime.registerConfigurationProvider(
jacksonProvider,
ConfigurationPriority.SCANNED
);
seedRuntime.registerConfigurationProvider(
propertiesProvider,
ConfigurationPriority.SCANNED
);
seedRuntime.registerConfigurationProvider(
jacksonOverrideProvider,
ConfigurationPriority.SCANNED_OVERRIDE
);
seedRuntime.registerConfigurationProvider(
propertiesOverrideProvider,
ConfigurationPriority.SCANNED_OVERRIDE
);
}
private Set<String> retrieveConfigurationResources(InitContext initContext) {
Set<String> allConfigurationResources = Sets.newHashSet();
allConfigurationResources.addAll(collectConfigResources(initContext, YAML_REGEX));
allConfigurationResources.addAll(collectConfigResources(initContext, YML_REGEX));
allConfigurationResources.addAll(collectConfigResources(initContext, JSON_REGEX));
allConfigurationResources.addAll(collectConfigResources(initContext, PROPERTIES_REGEX));
return allConfigurationResources;
}
private List<String> collectConfigResources(InitContext initContext, String regex) {
return initContext.mapResourcesByRegex()
.get(regex)
.stream()
.filter(propsResource -> propsResource.startsWith(CONFIGURATION_LOCATION))
.collect(Collectors.toList());
}
private boolean isOverrideResource(String configurationResource) {
return configurationResource.endsWith(".override.yaml") ||
configurationResource.endsWith(".override.yml") ||
configurationResource.endsWith(".override.json") ||
configurationResource.endsWith(".override.properties");
}
private boolean isJacksonResource(String configurationResource) {
return configurationResource.endsWith(".yaml") ||
configurationResource.endsWith(".yml") ||
configurationResource.endsWith(".json");
}
private boolean isPropertiesResource(String configurationResource) {
return configurationResource.endsWith(".properties");
}
@Override
public Object nativeUnitModule() {
return new ConfigurationModule(application);
}
@Override
public Application getApplication() {
return application;
}
}