Support nested @PropertyMapping annotations

Update `AnnotationsPropertySource` so that nested annotations are
supported. Prior to this commit, annotations annotated with
`@PropertyMapping` that contained nested annotation attributes would
result in instances of `TypeMappedAnnotation` being used as properties.
This usually led to errors due to not being able to convert those to
Strings. This commit makes it so that nested annotations are recursively
mapped to properties. This should allow for more complex configuration
to be mapped from annotations.

See gh-23146
This commit is contained in:
Stefan Zwanenburg 2020-08-31 12:21:20 +02:00 committed by Phillip Webb
parent f60f3cb38e
commit edf4c833c2
2 changed files with 79 additions and 3 deletions

View File

@ -90,7 +90,7 @@ public class AnnotationsPropertySource extends EnumerablePropertySource<Class<?>
}
}
String name = getName(prefix, attributeMapping, attribute);
putProperties(name, value.get(), properties);
putProperties(name, skip, value.get(), properties);
}
private String getName(String prefix, MergedAnnotation<?> attributeMapping, Method attribute) {
@ -118,11 +118,17 @@ public class AnnotationsPropertySource extends EnumerablePropertySource<Class<?>
return postfix;
}
private void putProperties(String name, Object value, Map<String, Object> properties) {
private void putProperties(String name, SkipPropertyMapping defaultSkip, Object value,
Map<String, Object> properties) {
if (ObjectUtils.isArray(value)) {
Object[] array = ObjectUtils.toObjectArray(value);
for (int i = 0; i < array.length; i++) {
properties.put(name + "[" + i + "]", array[i]);
putProperties(name + "[" + i + "]", defaultSkip, array[i], properties);
}
}
else if (value instanceof MergedAnnotation<?>) {
for (Method attribute : ((MergedAnnotation<?>) value).getType().getDeclaredMethods()) {
collectProperties(name, defaultSkip, (MergedAnnotation<?>) value, attribute, properties);
}
}
else {

View File

@ -21,6 +21,9 @@ import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.properties.AnnotationsPropertySourceTests.DeeplyNestedAnnotations.Level1;
import org.springframework.boot.test.autoconfigure.properties.AnnotationsPropertySourceTests.DeeplyNestedAnnotations.Level2;
import org.springframework.boot.test.autoconfigure.properties.AnnotationsPropertySourceTests.NestedAnnotations.Entry;
import org.springframework.core.annotation.AliasFor;
import static org.assertj.core.api.Assertions.assertThat;
@ -172,6 +175,26 @@ class AnnotationsPropertySourceTests {
assertThat(source.containsProperty("testenum.value")).isFalse();
}
@Test
void nestedAnnotationsMapped() {
AnnotationsPropertySource source = new AnnotationsPropertySource(PropertyMappedWithNestedAnnotations.class);
assertThat(source.getProperty("testnested")).isNull();
assertThat(source.getProperty("testnested.entries[0]")).isNull();
assertThat(source.getProperty("testnested.entries[0].value")).isEqualTo("one");
assertThat(source.getProperty("testnested.entries[1]")).isNull();
assertThat(source.getProperty("testnested.entries[1].value")).isEqualTo("two");
}
@Test
void deeplyNestedAnnotationsMapped() {
AnnotationsPropertySource source = new AnnotationsPropertySource(
PropertyMappedWithDeeplyNestedAnnotations.class);
assertThat(source.getProperty("testdeeplynested")).isNull();
assertThat(source.getProperty("testdeeplynested.level1")).isNull();
assertThat(source.getProperty("testdeeplynested.level1.level2")).isNull();
assertThat(source.getProperty("testdeeplynested.level1.level2.value")).isEqualTo("level2");
}
static class NoAnnotation {
}
@ -396,4 +419,51 @@ class AnnotationsPropertySourceTests {
}
@Retention(RetentionPolicy.RUNTIME)
@PropertyMapping("testnested")
@interface NestedAnnotations {
Entry[] entries();
@Retention(RetentionPolicy.RUNTIME)
@interface Entry {
String value();
}
}
@NestedAnnotations(entries = { @Entry("one"), @Entry("two") })
static class PropertyMappedWithNestedAnnotations {
}
@Retention(RetentionPolicy.RUNTIME)
@PropertyMapping("testdeeplynested")
@interface DeeplyNestedAnnotations {
Level1 level1();
@Retention(RetentionPolicy.RUNTIME)
@interface Level1 {
Level2 level2();
}
@Retention(RetentionPolicy.RUNTIME)
@interface Level2 {
String value();
}
}
@DeeplyNestedAnnotations(level1 = @Level1(level2 = @Level2("level2")))
static class PropertyMappedWithDeeplyNestedAnnotations {
}
}