Skip to content

Commit 7a23a12

Browse files
committed
Fix configprops endpoint's handling of config tree values
Fixes gh-27327
1 parent 2d30061 commit 7a23a12

File tree

2 files changed

+112
-1
lines changed

2 files changed

+112
-1
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ private Map<String, Object> applyInput(String qualifiedKey) {
298298

299299
private Map<String, Object> getInput(String property, ConfigurationProperty candidate) {
300300
Map<String, Object> input = new LinkedHashMap<>();
301-
Object value = candidate.getValue();
301+
Object value = stringifyIfNecessary(candidate.getValue());
302302
Origin origin = Origin.from(candidate);
303303
List<Origin> originParents = Origin.parentsFrom(candidate);
304304
input.put("value", this.sanitizer.sanitize(property, value));
@@ -309,6 +309,16 @@ private Map<String, Object> getInput(String property, ConfigurationProperty cand
309309
return input;
310310
}
311311

312+
private Object stringifyIfNecessary(Object value) {
313+
if (value == null || value.getClass().isPrimitive()) {
314+
return value;
315+
}
316+
if (CharSequence.class.isAssignableFrom(value.getClass())) {
317+
return value.toString();
318+
}
319+
return "Complex property value " + value.getClass().getName();
320+
}
321+
312322
private String getQualifiedKey(String prefix, String key) {
313323
return (prefix.isEmpty() ? prefix : prefix + ".") + key;
314324
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616

1717
package org.springframework.boot.actuate.context.properties;
1818

19+
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
21+
import java.io.InputStream;
1922
import java.net.InetAddress;
2023
import java.util.ArrayList;
24+
import java.util.Collections;
2125
import java.util.HashMap;
2226
import java.util.List;
2327
import java.util.Map;
@@ -28,11 +32,16 @@
2832
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
2933
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
3034
import org.springframework.boot.context.properties.ConfigurationProperties;
35+
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
3136
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3237
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3338
import org.springframework.context.annotation.Bean;
3439
import org.springframework.context.annotation.Configuration;
3540
import org.springframework.context.annotation.Import;
41+
import org.springframework.core.convert.converter.Converter;
42+
import org.springframework.core.env.ConfigurableEnvironment;
43+
import org.springframework.core.env.MapPropertySource;
44+
import org.springframework.core.io.InputStreamSource;
3645

3746
import static org.assertj.core.api.Assertions.assertThat;
3847
import static org.assertj.core.api.Assertions.entry;
@@ -238,6 +247,43 @@ void hikariDataSourceConfigurationPropertiesBeanCanBeSerialized() {
238247
});
239248
}
240249

250+
@Test
251+
@SuppressWarnings("unchecked")
252+
void endpointResponseUsesToStringOfCharSequenceAsPropertyValue() throws IOException {
253+
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withInitializer((context) -> {
254+
ConfigurableEnvironment environment = context.getEnvironment();
255+
environment.getPropertySources().addFirst(new MapPropertySource("test",
256+
Collections.singletonMap("foo.name", new CharSequenceProperty("Spring Boot"))));
257+
}).withUserConfiguration(FooConfig.class);
258+
contextRunner.run((context) -> {
259+
ConfigurationPropertiesReportEndpoint endpoint = context
260+
.getBean(ConfigurationPropertiesReportEndpoint.class);
261+
ApplicationConfigurationProperties applicationProperties = endpoint.configurationProperties();
262+
ConfigurationPropertiesBeanDescriptor descriptor = applicationProperties.getContexts().get(context.getId())
263+
.getBeans().get("foo");
264+
assertThat((Map<String, Object>) descriptor.getInputs().get("name")).containsEntry("value", "Spring Boot");
265+
});
266+
}
267+
268+
@Test
269+
@SuppressWarnings("unchecked")
270+
void endpointResponseUsesPlaceholderForComplexValueAsPropertyValue() throws IOException {
271+
ApplicationContextRunner contextRunner = new ApplicationContextRunner().withInitializer((context) -> {
272+
ConfigurableEnvironment environment = context.getEnvironment();
273+
environment.getPropertySources().addFirst(new MapPropertySource("test",
274+
Collections.singletonMap("foo.name", new ComplexProperty("Spring Boot"))));
275+
}).withUserConfiguration(ComplexPropertyToStringConverter.class, FooConfig.class);
276+
contextRunner.run((context) -> {
277+
ConfigurationPropertiesReportEndpoint endpoint = context
278+
.getBean(ConfigurationPropertiesReportEndpoint.class);
279+
ApplicationConfigurationProperties applicationProperties = endpoint.configurationProperties();
280+
ConfigurationPropertiesBeanDescriptor descriptor = applicationProperties.getContexts().get(context.getId())
281+
.getBeans().get("foo");
282+
assertThat((Map<String, Object>) descriptor.getInputs().get("name")).containsEntry("value",
283+
"Complex property value " + ComplexProperty.class.getName());
284+
});
285+
}
286+
241287
@Configuration(proxyBeanMethods = false)
242288
@EnableConfigurationProperties
243289
static class Base {
@@ -518,4 +564,59 @@ HikariDataSource hikariDataSource() {
518564

519565
}
520566

567+
static class CharSequenceProperty implements CharSequence, InputStreamSource {
568+
569+
private final String value;
570+
571+
CharSequenceProperty(String value) {
572+
this.value = value;
573+
}
574+
575+
@Override
576+
public int length() {
577+
return this.value.length();
578+
}
579+
580+
@Override
581+
public char charAt(int index) {
582+
return this.value.charAt(index);
583+
}
584+
585+
@Override
586+
public CharSequence subSequence(int start, int end) {
587+
return this.value.subSequence(start, end);
588+
}
589+
590+
@Override
591+
public String toString() {
592+
return this.value;
593+
}
594+
595+
@Override
596+
public InputStream getInputStream() throws IOException {
597+
return new ByteArrayInputStream(this.value.getBytes());
598+
}
599+
600+
}
601+
602+
static class ComplexProperty {
603+
604+
private final String value;
605+
606+
ComplexProperty(String value) {
607+
this.value = value;
608+
}
609+
610+
}
611+
612+
@ConfigurationPropertiesBinding
613+
static class ComplexPropertyToStringConverter implements Converter<ComplexProperty, String> {
614+
615+
@Override
616+
public String convert(ComplexProperty source) {
617+
return source.value;
618+
}
619+
620+
}
621+
521622
}

0 commit comments

Comments
 (0)