io.micronaut.context.env.DefaultEnvironment Maven / Gradle / Ivy
/*
* Copyright 2017-2023 original authors
*
* 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
*
* https://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 io.micronaut.context.env;
import io.micronaut.context.ApplicationContextConfiguration;
import io.micronaut.context.exceptions.ConfigurationException;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.MutableConversionService;
import io.micronaut.core.convert.TypeConverter;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.core.io.ResourceResolver;
import io.micronaut.core.io.file.DefaultFileSystemResourceLoader;
import io.micronaut.core.io.file.FileSystemResourceLoader;
import io.micronaut.core.io.scan.BeanIntrospectionScanner;
import io.micronaut.core.io.scan.ClassPathResourceLoader;
import io.micronaut.core.io.service.SoftServiceLoader;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.optim.StaticOptimizations;
import io.micronaut.core.order.OrderUtil;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.BeanConfiguration;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.net.InetAddress;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* The default implementation of the {@link Environment} interface. Configures a named environment.
*
* @author Graeme Rocher
* @author rvanderwerf
* @since 1.0
*/
public class DefaultEnvironment extends PropertySourcePropertyResolver implements Environment {
private static final List CONSTANT_PROPERTY_SOURCES = StaticOptimizations.get(ConstantPropertySources.class)
.map(ConstantPropertySources::getSources)
.orElse(Collections.emptyList());
private static final String EC2_LINUX_HYPERVISOR_FILE = "/sys/hypervisor/uuid";
private static final String EC2_LINUX_BIOS_VENDOR_FILE = "/sys/devices/virtual/dmi/id/bios_vendor";
private static final String EC2_WINDOWS_HYPERVISOR_CMD = "wmic path win32_computersystemproduct get uuid";
private static final String FILE_SEPARATOR = ",";
private static final String AWS_LAMBDA_FUNCTION_NAME_ENV = "AWS_LAMBDA_FUNCTION_NAME";
private static final String K8S_ENV = "KUBERNETES_SERVICE_HOST";
private static final String PCF_ENV = "VCAP_SERVICES";
private static final String HEROKU_DYNO = "DYNO";
private static final String GOOGLE_APPENGINE_ENVIRONMENT = "GAE_ENV";
private static final int DEFAULT_READ_TIMEOUT = 500;
private static final int DEFAULT_CONNECT_TIMEOUT = 500;
// CHECKSTYLE:OFF
private static final String GOOGLE_COMPUTE_METADATA = "metadata.google.internal";
// CHECKSTYLE:ON
private static final String ORACLE_CLOUD_ASSET_TAG_FILE = "/sys/devices/virtual/dmi/id/chassis_asset_tag";
private static final String ORACLE_CLOUD_WINDOWS_ASSET_TAG_CMD = "wmic systemenclosure get smbiosassettag";
private static final String DO_SYS_VENDOR_FILE = "/sys/devices/virtual/dmi/id/sys_vendor";
private static final Boolean DEDUCE_ENVIRONMENT_DEFAULT = true;
private static final List DEFAULT_CONFIG_LOCATIONS = Arrays.asList("classpath:/", "file:config/");
protected final ClassPathResourceLoader resourceLoader;
protected final List refreshablePropertySources = new ArrayList<>(10);
protected final MutableConversionService mutableConversionService;
private EnvironmentsAndPackage environmentsAndPackage;
private final Set names;
private final ClassLoader classLoader;
private final Collection packages = new ConcurrentLinkedQueue<>();
private final BeanIntrospectionScanner annotationScanner;
private final Collection configurationIncludes = new HashSet<>(3);
private final Collection configurationExcludes = new HashSet<>(3);
private final AtomicBoolean running = new AtomicBoolean(false);
private Collection propertySourceLoaderList;
private final Map loaderByFormatMap = new ConcurrentHashMap<>();
private final Map presenceCache = new ConcurrentHashMap<>();
private final AtomicBoolean reading = new AtomicBoolean(false);
private final Boolean deduceEnvironments;
private final ApplicationContextConfiguration configuration;
private final Collection configLocations;
/**
* Construct a new environment for the given configuration.
*
* @param configuration The configuration
*/
public DefaultEnvironment(@NonNull ApplicationContextConfiguration configuration) {
this(configuration, true);
}
/**
* Construct a new environment for the given configuration.
*
* @param configuration The configuration
* @param logEnabled flag to enable or disable logger
*/
public DefaultEnvironment(@NonNull ApplicationContextConfiguration configuration, boolean logEnabled) {
super(configuration.getConversionService().orElseGet(MutableConversionService::create), logEnabled);
this.mutableConversionService = (MutableConversionService) conversionService;
this.configuration = configuration;
this.resourceLoader = configuration.getResourceLoader();
List specifiedNames = new ArrayList<>(configuration.getEnvironments());
specifiedNames.addAll(0, Stream.of(CachedEnvironment.getProperty(ENVIRONMENTS_PROPERTY),
CachedEnvironment.getenv(ENVIRONMENTS_ENV))
.filter(StringUtils::isNotEmpty)
.flatMap(s -> Arrays.stream(s.split(",")))
.map(String::trim)
.toList());
this.deduceEnvironments = configuration.getDeduceEnvironments().orElse(null);
EnvironmentsAndPackage environmentsAndPackage = getEnvironmentsAndPackage(specifiedNames);
if (environmentsAndPackage.enviroments.isEmpty() && specifiedNames.isEmpty()) {
specifiedNames = configuration.getDefaultEnvironments();
}
Set environments = new LinkedHashSet<>(environmentsAndPackage.enviroments);
String aPackage = environmentsAndPackage.aPackage;
if (aPackage != null) {
packages.add(aPackage);
}
specifiedNames.forEach(environments::remove);
environments.addAll(specifiedNames);
this.classLoader = configuration.getClassLoader();
this.annotationScanner = createAnnotationScanner(classLoader);
this.names = environments;
if (!environments.isEmpty()) {
log.info("Established active environments: {}", environments);
}
List configLocations = configuration.getOverrideConfigLocations() == null ?
new ArrayList<>(DEFAULT_CONFIG_LOCATIONS) : configuration.getOverrideConfigLocations();
// Search config locations in reverse order
Collections.reverse(configLocations);
this.configLocations = configLocations;
}
@Override
public boolean isPresent(String className) {
return presenceCache.computeIfAbsent(className, s -> ClassUtils.isPresent(className, getClassLoader()));
}
@Override
public PropertyPlaceholderResolver getPlaceholderResolver() {
return this.propertyPlaceholderResolver;
}
@Override
public Stream> scan(Class extends Annotation> annotation) {
return annotationScanner.scan(annotation, getPackages());
}
@Override
public Stream> scan(Class extends Annotation> annotation, String... packages) {
return annotationScanner.scan(annotation, packages);
}
@Override
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public boolean isActive(BeanConfiguration configuration) {
String name = configuration.getName();
return !configurationExcludes.contains(name) && (configurationIncludes.isEmpty() || configurationIncludes.contains(name));
}
@Override
public DefaultEnvironment addPropertySource(PropertySource propertySource) {
propertySources.put(propertySource.getName(), propertySource);
if (isRunning() && !reading.get()) {
resetCaches();
processPropertySource(propertySource, PropertySource.PropertyConvention.JAVA_PROPERTIES);
}
return this;
}
@Override
public Environment removePropertySource(PropertySource propertySource) {
propertySources.remove(propertySource.getName());
if (isRunning() && !reading.get()) {
resetCaches();
}
return this;
}
@Override
public DefaultEnvironment addPropertySource(String name, Map values) {
return (DefaultEnvironment) super.addPropertySource(name, values);
}
@Override
public Environment addPackage(String pkg) {
if (!this.packages.contains(pkg)) {
this.packages.add(pkg);
}
return this;
}
@Override
public Environment addConfigurationExcludes(@Nullable String... names) {
if (names != null) {
configurationExcludes.addAll(Arrays.asList(names));
}
return this;
}
@Override
public Environment addConfigurationIncludes(String... names) {
if (names != null) {
configurationIncludes.addAll(Arrays.asList(names));
}
return this;
}
@Override
public Collection getPackages() {
return Collections.unmodifiableCollection(packages);
}
@Override
public Set getActiveNames() {
return this.names;
}
@Override
public Collection getPropertySources() {
return Collections.unmodifiableCollection(this.propertySources.values());
}
@Override
public Environment start() {
if (running.compareAndSet(false, true)) {
log.debug("Starting environment {} for active names {}", this, getActiveNames());
if (reading.compareAndSet(false, true)) {
readPropertySources(getPropertySourceRootName());
reading.set(false);
}
}
return this;
}
@Override
public boolean isRunning() {
return running.get();
}
@Override
public Environment stop() {
running.set(false);
reading.set(false);
this.propertySources.values().removeAll(refreshablePropertySources);
synchronized (catalog) {
Arrays.fill(catalog, null);
resetCaches();
}
return this;
}
@Override
public Map refreshAndDiff() {
Map[] copiedCatalog = copyCatalog();
refresh();
return diffCatalog(copiedCatalog, catalog);
}
@Override
public Optional convert(Object object, Class targetType, ConversionContext context) {
return mutableConversionService.convert(object, targetType, context);
}
@Override
public Optional convert(S object, Class super S> sourceType, Class targetType, ConversionContext context) {
return mutableConversionService.convert(object, sourceType, targetType, context);
}
@Override
public boolean canConvert(Class sourceType, Class targetType) {
return mutableConversionService.canConvert(sourceType, targetType);
}
@Override
public void addConverter(Class sourceType, Class targetType, TypeConverter typeConverter) {
mutableConversionService.addConverter(sourceType, targetType, typeConverter);
}
@Override
public void addConverter(Class sourceType, Class targetType, Function typeConverter) {
mutableConversionService.addConverter(sourceType, targetType, typeConverter);
}
/**
* @return The mutable conversion service.
*/
@Internal
public MutableConversionService getMutableConversionService() {
return mutableConversionService;
}
@Override
public Optional getResourceAsStream(String path) {
return resourceLoader.getResourceAsStream(path);
}
@Override
public Optional getResource(String path) {
return resourceLoader.getResource(path);
}
@Override
public Stream getResources(String path) {
return resourceLoader.getResources(path);
}
@Override
public boolean supportsPrefix(String path) {
return resourceLoader.supportsPrefix(path);
}
@Override
public ResourceLoader forBase(String basePath) {
return resourceLoader.forBase(basePath);
}
/**
* @return Whether environment names and packages should be deduced
*/
protected boolean shouldDeduceEnvironments() {
if (deduceEnvironments != null) {
log.debug("Environment deduction was set explicitly via builder to: {}", deduceEnvironments);
return deduceEnvironments;
} else if (configuration.isEnableDefaultPropertySources()) {
String deduceProperty = CachedEnvironment.getProperty(Environment.DEDUCE_ENVIRONMENT_PROPERTY);
String deduceEnv = CachedEnvironment.getenv(Environment.DEDUCE_ENVIRONMENT_ENV);
if (StringUtils.isNotEmpty(deduceEnv)) {
boolean deduce = Boolean.parseBoolean(deduceEnv);
log.debug("Environment deduction was set via environment variable to: {}", deduce);
return deduce;
} else if (StringUtils.isNotEmpty(deduceProperty)) {
boolean deduce = Boolean.parseBoolean(deduceProperty);
log.debug("Environment deduction was set via system property to: {}", deduce);
return deduce;
} else {
boolean deduceDefault = DEDUCE_ENVIRONMENT_DEFAULT;
log.debug("Environment deduction is using the default of: {}", deduceDefault);
return deduceDefault;
}
} else {
return false;
}
}
/**
* @return Whether cloud environment should be deduced based on environment variable, system property or configuration
*/
protected boolean shouldDeduceCloudEnvironment() {
String deduceEnv = CachedEnvironment.getenv(Environment.DEDUCE_CLOUD_ENVIRONMENT_ENV);
if (StringUtils.isNotEmpty(deduceEnv)) {
boolean deduce = Boolean.parseBoolean(deduceEnv);
log.debug("Cloud environment deduction was set via environment variable to: {}", deduce);
return deduce;
}
String deduceProperty = CachedEnvironment.getProperty(Environment.DEDUCE_CLOUD_ENVIRONMENT_PROPERTY);
if (StringUtils.isNotEmpty(deduceProperty)) {
boolean deduce = Boolean.parseBoolean(deduceProperty);
log.debug("Cloud environment deduction was set via system property to: {}", deduce);
return deduce;
}
return configuration.isDeduceCloudEnvironment();
}
/**
* Creates the default annotation scanner.
*
* @param classLoader The class loader
* @return The scanner
*/
protected BeanIntrospectionScanner createAnnotationScanner(ClassLoader classLoader) {
return new BeanIntrospectionScanner();
}
/**
* @return The property source root name
*/
protected String getPropertySourceRootName() {
return DEFAULT_NAME;
}
/**
* @param name The name to read property sources
*/
protected void readPropertySources(String name) {
refreshablePropertySources.clear();
List propertySources;
if (configuration.isEnableDefaultPropertySources()) {
propertySources = readPropertySourceList(name);
addDefaultPropertySources(propertySources);
String propertySourcesSystemProperty = CachedEnvironment.getProperty(Environment.PROPERTY_SOURCES_KEY);
if (propertySourcesSystemProperty != null) {
propertySources.addAll(readPropertySourceListFromFiles(propertySourcesSystemProperty));
}
String propertySourcesEnv = readPropertySourceListKeyFromEnvironment();
if (propertySourcesEnv != null) {
propertySources.addAll(readPropertySourceListFromFiles(propertySourcesEnv));
}
refreshablePropertySources.addAll(propertySources);
readConstantPropertySources(name, propertySources);
} else {
propertySources = new ArrayList<>(this.propertySources.size());
}
propertySources.addAll(this.propertySources.values());
OrderUtil.sortOrdered(propertySources);
for (PropertySource propertySource : propertySources) {
log.debug("Processing property source: {}", propertySource.getName());
processPropertySource(propertySource, propertySource.getConvention());
}
}
private void readConstantPropertySources(String name, List propertySources) {
Set activeNames = getActiveNames();
Set propertySourceNames = CollectionUtils.newHashSet(activeNames.size() + 1);
propertySourceNames.add(name);
for (String env : activeNames) {
propertySourceNames.add(name + "-" + env);
}
for (PropertySource p : getConstantPropertySources()) {
if (propertySourceNames.contains(p.getName())) {
propertySources.add(p);
}
}
}
/**
* @return Property sources created at build time
*/
protected List getConstantPropertySources() {
return CONSTANT_PROPERTY_SOURCES;
}
/**
* Reads the value of MICRONAUT_CONFIG_FILES environment variable.
*
* @return The comma-separated list of files
*/
protected String readPropertySourceListKeyFromEnvironment() {
return CachedEnvironment.getenv(StringUtils.convertDotToUnderscore(Environment.PROPERTY_SOURCES_KEY));
}
/**
* Resolve the property sources for files passed via system property and system env.
*
* @param files The comma separated list of files
* @return The list of property sources for each file
*/
protected List readPropertySourceListFromFiles(String files) {
List propertySources = new ArrayList<>();
Collection propertySourceLoaders = getPropertySourceLoaders();
Optional> filePathList = Optional.ofNullable(files)
.filter(value -> !value.isEmpty())
.map(value -> value.split(FILE_SEPARATOR))
.map(Arrays::asList)
.map(Collections::unmodifiableList);
filePathList.ifPresent(list -> {
if (!list.isEmpty()) {
int order = AbstractPropertySourceLoader.DEFAULT_POSITION + 50;
for (String filePath: list) {
if (!propertySourceLoaders.isEmpty()) {
String extension = NameUtils.extension(filePath);
String fileName = NameUtils.filename(filePath);
Optional propertySourceLoader = Optional.ofNullable(loaderByFormatMap.get(extension));
if (propertySourceLoader.isPresent()) {
log.debug("Reading property sources from loader: {}", propertySourceLoader);
Optional