/* * Copyright 2012-2017 the original author or 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 * * 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 org.springframework.boot.context.properties.source; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; import org.springframework.boot.env.RandomValuePropertySource; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.PropertySourceOrigin; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.env.SystemEnvironmentPropertySource; import org.springframework.util.Assert; /** * {@link ConfigurationPropertySource} backed by a non-enumerable Spring * {@link PropertySource} or a restricted {@link EnumerablePropertySource} implementation * (such as a security restricted {@code systemEnvironment} source). A * {@link PropertySource} is adapted with the help of a {@link PropertyMapper} which * provides the mapping rules for individual properties. * <p> * Each {@link ConfigurationPropertySource#getConfigurationProperty * getConfigurationProperty} call attempts to * {@link PropertyMapper#map(PropertySource, ConfigurationPropertyName) map} the * {@link ConfigurationPropertyName} to one or more {@code String} based names. This * allows fast property resolution for well formed property sources. * <p> * When possible the {@link SpringIterableConfigurationPropertySource} will be used in * preference to this implementation since it supports full "relaxed" style resolution. * * @author Phillip Webb * @author Madhura Bhave * @see #from(PropertySource) * @see PropertyMapper * @see SpringIterableConfigurationPropertySource */ class SpringConfigurationPropertySource implements ConfigurationPropertySource { private static final ConfigurationPropertyName RANDOM = ConfigurationPropertyName .of("random"); private final PropertySource<?> propertySource; private final PropertyMapper mapper; private final Function<ConfigurationPropertyName, ConfigurationPropertyState> containsDescendantOfMethod; /** * Create a new {@link SpringConfigurationPropertySource} implementation. * @param propertySource the source property source * @param mapper the property mapper * @param containsDescendantOfMethod function used to implement * {@link #containsDescendantOf(ConfigurationPropertyName)} (may be {@code null}) */ SpringConfigurationPropertySource(PropertySource<?> propertySource, PropertyMapper mapper, Function<ConfigurationPropertyName, ConfigurationPropertyState> containsDescendantOfMethod) { Assert.notNull(propertySource, "PropertySource must not be null"); Assert.notNull(mapper, "Mapper must not be null"); this.propertySource = propertySource; this.mapper = new ExceptionSwallowingPropertyMapper(mapper); this.containsDescendantOfMethod = (containsDescendantOfMethod != null ? containsDescendantOfMethod : (n) -> ConfigurationPropertyState.UNKNOWN); } @Override public ConfigurationProperty getConfigurationProperty( ConfigurationPropertyName name) { List<PropertyMapping> mappings = getMapper().map(getPropertySource(), name); return find(mappings, name); } @Override public ConfigurationPropertyState containsDescendantOf( ConfigurationPropertyName name) { return this.containsDescendantOfMethod.apply(name); } protected final ConfigurationProperty find(List<PropertyMapping> mappings, ConfigurationPropertyName name) { return mappings.stream().filter((m) -> m.isApplicable(name)).map(this::find) .filter(Objects::nonNull).findFirst().orElse(null); } private ConfigurationProperty find(PropertyMapping mapping) { String propertySourceName = mapping.getPropertySourceName(); Object value = getPropertySource().getProperty(propertySourceName); if (value == null) { return null; } value = mapping.getValueExtractor().apply(value); ConfigurationPropertyName configurationPropertyName = mapping .getConfigurationPropertyName(); Origin origin = PropertySourceOrigin.get(this.propertySource, propertySourceName); return ConfigurationProperty.of(configurationPropertyName, value, origin); } protected PropertySource<?> getPropertySource() { return this.propertySource; } protected final PropertyMapper getMapper() { return this.mapper; } @Override public String toString() { return this.propertySource.toString(); } /** * Create a new {@link SpringConfigurationPropertySource} for the specified * {@link PropertySource}. * @param source the source Spring {@link PropertySource} * @return a {@link SpringConfigurationPropertySource} or * {@link SpringIterableConfigurationPropertySource} instance */ public static SpringConfigurationPropertySource from(PropertySource<?> source) { Assert.notNull(source, "Source must not be null"); PropertyMapper mapper = getPropertyMapper(source); if (isFullEnumerable(source)) { return new SpringIterableConfigurationPropertySource( (EnumerablePropertySource<?>) source, mapper); } return new SpringConfigurationPropertySource(source, mapper, getContainsDescendantOfMethod(source)); } private static PropertyMapper getPropertyMapper(PropertySource<?> source) { if (source instanceof SystemEnvironmentPropertySource) { return SystemEnvironmentPropertyMapper.INSTANCE; } return DefaultPropertyMapper.INSTANCE; } private static boolean isFullEnumerable(PropertySource<?> source) { PropertySource<?> rootSource = getRootSource(source); if (rootSource.getSource() instanceof Map) { // Check we're not security restricted try { ((Map<?, ?>) rootSource.getSource()).size(); } catch (UnsupportedOperationException ex) { return false; } } return (source instanceof EnumerablePropertySource); } private static PropertySource<?> getRootSource(PropertySource<?> source) { while (source.getSource() != null && source.getSource() instanceof PropertySource) { source = (PropertySource<?>) source.getSource(); } return source; } private static Function<ConfigurationPropertyName, ConfigurationPropertyState> getContainsDescendantOfMethod( PropertySource<?> source) { if (source instanceof RandomValuePropertySource) { return (name) -> (name.isAncestorOf(RANDOM) || name.equals(RANDOM) ? ConfigurationPropertyState.PRESENT : ConfigurationPropertyState.ABSENT); } return null; } /** * {@link PropertyMapper} that swallows exceptions when the mapping fails. */ private static class ExceptionSwallowingPropertyMapper implements PropertyMapper { private final PropertyMapper mapper; ExceptionSwallowingPropertyMapper(PropertyMapper mapper) { this.mapper = mapper; } @Override public List<PropertyMapping> map(PropertySource<?> propertySource, ConfigurationPropertyName configurationPropertyName) { try { return this.mapper.map(propertySource, configurationPropertyName); } catch (Exception ex) { return Collections.emptyList(); } } @Override public List<PropertyMapping> map(PropertySource<?> propertySource, String propertySourceName) { try { return this.mapper.map(propertySource, propertySourceName); } catch (Exception ex) { return Collections.emptyList(); } } } }