diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySource.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySource.java index 9e7d4dfee4..a3eadd7342 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySource.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,10 +73,10 @@ public class AnnotationsPropertySource extends EnumerablePropertySource return properties; } - private void collectProperties(String prefix, SkipPropertyMapping defaultSkip, MergedAnnotation annotation, + private void collectProperties(String prefix, SkipPropertyMapping skip, MergedAnnotation annotation, Method attribute, Map properties) { MergedAnnotation attributeMapping = MergedAnnotations.from(attribute).get(PropertyMapping.class); - SkipPropertyMapping skip = attributeMapping.getValue("skip", SkipPropertyMapping.class).orElse(defaultSkip); + skip = attributeMapping.getValue("skip", SkipPropertyMapping.class).orElse(skip); if (skip == SkipPropertyMapping.YES) { return; } @@ -90,7 +90,7 @@ public class AnnotationsPropertySource extends EnumerablePropertySource } } 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,18 @@ public class AnnotationsPropertySource extends EnumerablePropertySource return postfix; } - private void putProperties(String name, Object value, Map properties) { + private void putProperties(String name, SkipPropertyMapping defaultSkip, Object value, + Map 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) { + MergedAnnotation annotation = (MergedAnnotation) value; + for (Method attribute : annotation.getType().getDeclaredMethods()) { + collectProperties(name, defaultSkip, (MergedAnnotation) value, attribute, properties); } } else { diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySourceTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySourceTests.java index 2744df4d8b..bbdf7a8855 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySourceTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 { + + } + }