From 7a23a12ce08106f281e3a563bf2558d575ed1a36 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 15 Jul 2021 11:49:00 +0100 Subject: [PATCH] Fix configprops endpoint's handling of config tree values Fixes gh-27327 --- ...ConfigurationPropertiesReportEndpoint.java | 12 ++- ...rtiesReportEndpointSerializationTests.java | 101 ++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index 57a12e331bcb..886168b9ebbf 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -298,7 +298,7 @@ private Map applyInput(String qualifiedKey) { private Map getInput(String property, ConfigurationProperty candidate) { Map input = new LinkedHashMap<>(); - Object value = candidate.getValue(); + Object value = stringifyIfNecessary(candidate.getValue()); Origin origin = Origin.from(candidate); List originParents = Origin.parentsFrom(candidate); input.put("value", this.sanitizer.sanitize(property, value)); @@ -309,6 +309,16 @@ private Map getInput(String property, ConfigurationProperty cand 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; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java index 531bfa2eb311..8dcb6b49e17c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java @@ -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.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 @@ void hikariDataSourceConfigurationPropertiesBeanCanBeSerialized() { }); } + @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) 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) descriptor.getInputs().get("name")).containsEntry("value", + "Complex property value " + ComplexProperty.class.getName()); + }); + } + @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties static class Base { @@ -518,4 +564,59 @@ HikariDataSource hikariDataSource() { } + 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 { + + @Override + public String convert(ComplexProperty source) { + return source.value; + } + + } + }