Skip to content

Commit fbf3c48

Browse files
committed
Fix setter detection in configprops endpoint
Previously, the setter of a property whose second letter is upper-case ( such as `oAuth2Uri`) was not detected properly. The JavaBean spec states that, in such a case, the first letter should not be capitalized (i.e. the setter should be `setoAuth2Uri` rather than `setOAuth2Uri`). This commit makes sure that Jackson uses standard bean names and fixes the setter detection algorithm to take this case into account. Closes gh-13878
1 parent 9e4ccbd commit fbf3c48

File tree

2 files changed

+99
-3
lines changed

2 files changed

+99
-3
lines changed

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.fasterxml.jackson.annotation.JsonInclude.Include;
2727
import com.fasterxml.jackson.core.JsonGenerator;
2828
import com.fasterxml.jackson.databind.BeanDescription;
29+
import com.fasterxml.jackson.databind.MapperFeature;
2930
import com.fasterxml.jackson.databind.ObjectMapper;
3031
import com.fasterxml.jackson.databind.SerializationConfig;
3132
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -172,6 +173,7 @@ private Map<String, Object> safeSerialize(ObjectMapper mapper, Object bean,
172173
*/
173174
protected void configureObjectMapper(ObjectMapper mapper) {
174175
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
176+
mapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true);
175177
mapper.setSerializationInclusion(Include.NON_NULL);
176178
applyConfigurationPropertiesFilter(mapper);
177179
applySerializationModifier(mapper);
@@ -375,7 +377,7 @@ private boolean isReadable(BeanDescription beanDesc, BeanPropertyWriter writer)
375377

376378
private AnnotatedMethod findSetter(BeanDescription beanDesc,
377379
BeanPropertyWriter writer) {
378-
String name = "set" + StringUtils.capitalize(writer.getName());
380+
String name = "set" + determineAccessorSuffix(writer.getName());
379381
Class<?> type = writer.getType().getRawClass();
380382
AnnotatedMethod setter = beanDesc.findMethod(name, new Class<?>[] { type });
381383
// The enabled property of endpoints returns a boolean primitive but is set
@@ -386,6 +388,23 @@ private AnnotatedMethod findSetter(BeanDescription beanDesc,
386388
return setter;
387389
}
388390

391+
/**
392+
* Determine the accessor suffix of the specified {@code propertyName}, see
393+
* section 8.8 "Capitalization of inferred names" of the JavaBean specs for more
394+
* details.
395+
* @param propertyName the property name to turn into an accessor suffix
396+
* @return the accessor suffix for {@code propertyName}
397+
*/
398+
private String determineAccessorSuffix(String propertyName) {
399+
if (propertyName.length() > 1
400+
&& Character.isUpperCase(propertyName.charAt(1))) {
401+
return propertyName;
402+
}
403+
else {
404+
return StringUtils.capitalize(propertyName);
405+
}
406+
}
407+
389408
}
390409

391410
/**

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

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
*
4545
* @author Dave Syer
4646
* @author Andy Wilkinson
47+
* @author Stephane Nicoll
4748
*/
4849
public class ConfigurationPropertiesReportEndpointTests {
4950

@@ -120,6 +121,24 @@ public void keySanitizationWithCustomPatternUsingCompositeKeys() {
120121
});
121122
}
122123

124+
@Test
125+
public void nonCamelCaseProperty() {
126+
load((context, properties) -> {
127+
Map<String, Object> nestedProperties = properties.getBeans()
128+
.get("testProperties").getProperties();
129+
assertThat(nestedProperties.get("myURL")).isEqualTo("https://example.com");
130+
});
131+
}
132+
133+
@Test
134+
public void simpleBoolean() {
135+
load((context, properties) -> {
136+
Map<String, Object> nestedProperties = properties.getBeans()
137+
.get("testProperties").getProperties();
138+
assertThat(nestedProperties.get("simpleBoolean")).isEqualTo(true);
139+
});
140+
}
141+
123142
@Test
124143
public void mixedBoolean() {
125144
load((context, properties) -> {
@@ -129,6 +148,24 @@ public void mixedBoolean() {
129148
});
130149
}
131150

151+
@Test
152+
public void mixedCase() {
153+
load((context, properties) -> {
154+
Map<String, Object> nestedProperties = properties.getBeans()
155+
.get("testProperties").getProperties();
156+
assertThat(nestedProperties.get("mIxedCase")).isEqualTo("mixed");
157+
});
158+
}
159+
160+
@Test
161+
public void singleLetterProperty() {
162+
load((context, properties) -> {
163+
Map<String, Object> nestedProperties = properties.getBeans()
164+
.get("testProperties").getProperties();
165+
assertThat(nestedProperties.get("z")).isEqualTo("zzz");
166+
});
167+
}
168+
132169
@Test
133170
@SuppressWarnings("unchecked")
134171
public void listsAreSanitized() {
@@ -219,8 +256,16 @@ public static class TestProperties {
219256

220257
private String myTestProperty = "654321";
221258

259+
private String myURL = "https://example.com";
260+
261+
private boolean simpleBoolean = true;
262+
222263
private Boolean mixedBoolean = true;
223264

265+
private String mIxedCase = "mixed";
266+
267+
private String z = "zzz";
268+
224269
private Map<String, Object> secrets = new HashMap<>();
225270

226271
private Hidden hidden = new Hidden();
@@ -254,14 +299,46 @@ public void setMyTestProperty(String myTestProperty) {
254299
this.myTestProperty = myTestProperty;
255300
}
256301

257-
public boolean isMixedBoolean() {
258-
return (this.mixedBoolean != null) ? this.mixedBoolean : false;
302+
public String getMyURL() {
303+
return this.myURL;
304+
}
305+
306+
public void setMyURL(String myURL) {
307+
this.myURL = myURL;
308+
}
309+
310+
public boolean isSimpleBoolean() {
311+
return this.simpleBoolean;
312+
}
313+
314+
public void setSimpleBoolean(boolean simpleBoolean) {
315+
this.simpleBoolean = simpleBoolean;
259316
}
260317

261318
public void setMixedBoolean(Boolean mixedBoolean) {
262319
this.mixedBoolean = mixedBoolean;
263320
}
264321

322+
public boolean isMixedBoolean() {
323+
return (this.mixedBoolean != null) ? this.mixedBoolean : false;
324+
}
325+
326+
public String getmIxedCase() {
327+
return this.mIxedCase;
328+
}
329+
330+
public void setmIxedCase(String mIxedCase) {
331+
this.mIxedCase = mIxedCase;
332+
}
333+
334+
public String getZ() {
335+
return this.z;
336+
}
337+
338+
public void setZ(String z) {
339+
this.z = z;
340+
}
341+
265342
public Map<String, Object> getSecrets() {
266343
return this.secrets;
267344
}

0 commit comments

Comments
 (0)