Fix configprops endpoint's handling of config tree values

Fixes gh-27327
This commit is contained in:
Andy Wilkinson 2021-07-15 11:49:00 +01:00
parent 2d3006171d
commit 7a23a12ce0
2 changed files with 112 additions and 1 deletions

View File

@ -298,7 +298,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
private Map<String, Object> getInput(String property, ConfigurationProperty candidate) {
Map<String, Object> input = new LinkedHashMap<>();
Object value = candidate.getValue();
Object value = stringifyIfNecessary(candidate.getValue());
Origin origin = Origin.from(candidate);
List<Origin> originParents = Origin.parentsFrom(candidate);
input.put("value", this.sanitizer.sanitize(property, value));
@ -309,6 +309,16 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
return input;
}
private Object stringifyIfNecessary(Object value) {
if (value == null || value.getClass().isPrimitive()) {
return value;
}
if (CharSequence.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
return "Complex property value " + value.getClass().getName();
}
private String getQualifiedKey(String prefix, String key) {
return (prefix.isEmpty() ? prefix : prefix + ".") + key;
}

View File

@ -16,8 +16,12 @@
package org.springframework.boot.actuate.context.properties;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -28,11 +32,16 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.io.InputStreamSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
@ -238,6 +247,43 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
});
}
@Test
@SuppressWarnings("unchecked")
void endpointResponseUsesToStringOfCharSequenceAsPropertyValue() throws IOException {
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withInitializer((context) -> {
ConfigurableEnvironment environment = context.getEnvironment();
environment.getPropertySources().addFirst(new MapPropertySource("test",
Collections.singletonMap("foo.name", new CharSequenceProperty("Spring Boot"))));
}).withUserConfiguration(FooConfig.class);
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ApplicationConfigurationProperties applicationProperties = endpoint.configurationProperties();
ConfigurationPropertiesBeanDescriptor descriptor = applicationProperties.getContexts().get(context.getId())
.getBeans().get("foo");
assertThat((Map<String, Object>) descriptor.getInputs().get("name")).containsEntry("value", "Spring Boot");
});
}
@Test
@SuppressWarnings("unchecked")
void endpointResponseUsesPlaceholderForComplexValueAsPropertyValue() throws IOException {
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withInitializer((context) -> {
ConfigurableEnvironment environment = context.getEnvironment();
environment.getPropertySources().addFirst(new MapPropertySource("test",
Collections.singletonMap("foo.name", new ComplexProperty("Spring Boot"))));
}).withUserConfiguration(ComplexPropertyToStringConverter.class, FooConfig.class);
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ApplicationConfigurationProperties applicationProperties = endpoint.configurationProperties();
ConfigurationPropertiesBeanDescriptor descriptor = applicationProperties.getContexts().get(context.getId())
.getBeans().get("foo");
assertThat((Map<String, Object>) descriptor.getInputs().get("name")).containsEntry("value",
"Complex property value " + ComplexProperty.class.getName());
});
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
static class Base {
@ -518,4 +564,59 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
}
static class CharSequenceProperty implements CharSequence, InputStreamSource {
private final String value;
CharSequenceProperty(String value) {
this.value = value;
}
@Override
public int length() {
return this.value.length();
}
@Override
public char charAt(int index) {
return this.value.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return this.value.subSequence(start, end);
}
@Override
public String toString() {
return this.value;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.value.getBytes());
}
}
static class ComplexProperty {
private final String value;
ComplexProperty(String value) {
this.value = value;
}
}
@ConfigurationPropertiesBinding
static class ComplexPropertyToStringConverter implements Converter<ComplexProperty, String> {
@Override
public String convert(ComplexProperty source) {
return source.value;
}
}
}