/*
* 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.actuate.endpoint;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.StandardEnvironment;
/**
* {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information.
*
* @author Dave Syer
* @author Phillip Webb
* @author Christian Dupuis
* @author Madhura Bhave
*/
@ConfigurationProperties(prefix = "endpoints.env")
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
private final Sanitizer sanitizer = new Sanitizer();
/**
* Create a new {@link EnvironmentEndpoint} instance.
*/
public EnvironmentEndpoint() {
super("env");
}
public void setKeysToSanitize(String... keysToSanitize) {
this.sanitizer.setKeysToSanitize(keysToSanitize);
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("profiles", getEnvironment().getActiveProfiles());
PropertyResolver resolver = getResolver();
for (Entry<String, PropertySource<?>> entry : getPropertySourcesAsMap()
.entrySet()) {
PropertySource<?> source = entry.getValue();
String sourceName = entry.getKey();
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
Map<String, Object> properties = new LinkedHashMap<>();
for (String name : enumerable.getPropertyNames()) {
Object resolved = resolver.getProperty(name, Object.class);
properties.put(name, sanitize(name, resolved));
}
properties = postProcessSourceProperties(sourceName, properties);
if (properties != null) {
result.put(sourceName, properties);
}
}
}
return result;
}
public PropertyResolver getResolver() {
PlaceholderSanitizingPropertyResolver resolver = new PlaceholderSanitizingPropertyResolver(
getPropertySources(), this.sanitizer);
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
return resolver;
}
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
Map<String, PropertySource<?>> map = new LinkedHashMap<String, PropertySource<?>>();
for (PropertySource<?> source : getPropertySources()) {
extract("", map, source);
}
return map;
}
private MutablePropertySources getPropertySources() {
MutablePropertySources sources;
Environment environment = getEnvironment();
if (environment != null && environment instanceof ConfigurableEnvironment) {
sources = ((ConfigurableEnvironment) environment).getPropertySources();
}
else {
sources = new StandardEnvironment().getPropertySources();
}
return sources;
}
private void extract(String root, Map<String, PropertySource<?>> map,
PropertySource<?> source) {
if (source instanceof CompositePropertySource) {
for (PropertySource<?> nest : ((CompositePropertySource) source)
.getPropertySources()) {
extract(source.getName() + ":", map, nest);
}
}
else {
map.put(root + source.getName(), source);
}
}
public Object sanitize(String name, Object object) {
return this.sanitizer.sanitize(name, object);
}
/**
* Apply any post processing to source data before it is added.
* @param sourceName the source name
* @param properties the properties
* @return the post-processed properties or {@code null} if the source should not be
* added
* @since 1.4.0
*/
protected Map<String, Object> postProcessSourceProperties(String sourceName,
Map<String, Object> properties) {
return properties;
}
/**
* {@link PropertySourcesPropertyResolver} that sanitizes sensitive placeholders if
* present.
*/
private class PlaceholderSanitizingPropertyResolver
extends PropertySourcesPropertyResolver {
private final Sanitizer sanitizer;
/**
* Create a new resolver against the given property sources.
* @param propertySources the set of {@link PropertySource} objects to use
* @param sanitizer the sanitizer used to sanitize sensitive values
*/
PlaceholderSanitizingPropertyResolver(PropertySources propertySources,
Sanitizer sanitizer) {
super(propertySources);
this.sanitizer = sanitizer;
}
@Override
protected String getPropertyAsRawString(String key) {
String value = super.getPropertyAsRawString(key);
return (String) this.sanitizer.sanitize(key, value);
}
}
}