Commit edf4c833 authored by Stefan Zwanenburg's avatar Stefan Zwanenburg Committed by Phillip Webb

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
parent f60f3cb3
......@@ -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 {
......
......@@ -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 {
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment