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 d7275ba376..06515af9e6 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 @@ -26,6 +26,7 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializationFeature; @@ -172,6 +173,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext */ protected void configureObjectMapper(ObjectMapper mapper) { mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true); mapper.setSerializationInclusion(Include.NON_NULL); applyConfigurationPropertiesFilter(mapper); applySerializationModifier(mapper); @@ -375,7 +377,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext private AnnotatedMethod findSetter(BeanDescription beanDesc, BeanPropertyWriter writer) { - String name = "set" + StringUtils.capitalize(writer.getName()); + String name = "set" + determineAccessorSuffix(writer.getName()); Class type = writer.getType().getRawClass(); AnnotatedMethod setter = beanDesc.findMethod(name, new Class[] { type }); // The enabled property of endpoints returns a boolean primitive but is set @@ -386,6 +388,23 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext return setter; } + /** + * Determine the accessor suffix of the specified {@code propertyName}, see + * section 8.8 "Capitalization of inferred names" of the JavaBean specs for more + * details. + * @param propertyName the property name to turn into an accessor suffix + * @return the accessor suffix for {@code propertyName} + */ + private String determineAccessorSuffix(String propertyName) { + if (propertyName.length() > 1 + && Character.isUpperCase(propertyName.charAt(1))) { + return propertyName; + } + else { + return StringUtils.capitalize(propertyName); + } + } + } /** diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java index 2c131961a1..cfc56337c5 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java @@ -44,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Dave Syer * @author Andy Wilkinson + * @author Stephane Nicoll */ public class ConfigurationPropertiesReportEndpointTests { @@ -120,6 +121,24 @@ public class ConfigurationPropertiesReportEndpointTests { }); } + @Test + public void nonCamelCaseProperty() { + load((context, properties) -> { + Map nestedProperties = properties.getBeans() + .get("testProperties").getProperties(); + assertThat(nestedProperties.get("myURL")).isEqualTo("https://example.com"); + }); + } + + @Test + public void simpleBoolean() { + load((context, properties) -> { + Map nestedProperties = properties.getBeans() + .get("testProperties").getProperties(); + assertThat(nestedProperties.get("simpleBoolean")).isEqualTo(true); + }); + } + @Test public void mixedBoolean() { load((context, properties) -> { @@ -129,6 +148,24 @@ public class ConfigurationPropertiesReportEndpointTests { }); } + @Test + public void mixedCase() { + load((context, properties) -> { + Map nestedProperties = properties.getBeans() + .get("testProperties").getProperties(); + assertThat(nestedProperties.get("mIxedCase")).isEqualTo("mixed"); + }); + } + + @Test + public void singleLetterProperty() { + load((context, properties) -> { + Map nestedProperties = properties.getBeans() + .get("testProperties").getProperties(); + assertThat(nestedProperties.get("z")).isEqualTo("zzz"); + }); + } + @Test @SuppressWarnings("unchecked") public void listsAreSanitized() { @@ -219,8 +256,16 @@ public class ConfigurationPropertiesReportEndpointTests { private String myTestProperty = "654321"; + private String myURL = "https://example.com"; + + private boolean simpleBoolean = true; + private Boolean mixedBoolean = true; + private String mIxedCase = "mixed"; + + private String z = "zzz"; + private Map secrets = new HashMap<>(); private Hidden hidden = new Hidden(); @@ -254,14 +299,46 @@ public class ConfigurationPropertiesReportEndpointTests { this.myTestProperty = myTestProperty; } - public boolean isMixedBoolean() { - return (this.mixedBoolean != null) ? this.mixedBoolean : false; + public String getMyURL() { + return this.myURL; + } + + public void setMyURL(String myURL) { + this.myURL = myURL; + } + + public boolean isSimpleBoolean() { + return this.simpleBoolean; + } + + public void setSimpleBoolean(boolean simpleBoolean) { + this.simpleBoolean = simpleBoolean; } public void setMixedBoolean(Boolean mixedBoolean) { this.mixedBoolean = mixedBoolean; } + public boolean isMixedBoolean() { + return (this.mixedBoolean != null) ? this.mixedBoolean : false; + } + + public String getmIxedCase() { + return this.mIxedCase; + } + + public void setmIxedCase(String mIxedCase) { + this.mIxedCase = mIxedCase; + } + + public String getZ() { + return this.z; + } + + public void setZ(String z) { + this.z = z; + } + public Map getSecrets() { return this.secrets; }