From edf4c833c2e7861750516e7c72980f599f0a4e5b Mon Sep 17 00:00:00 2001 From: Stefan Zwanenburg Date: Mon, 31 Aug 2020 12:21:20 +0200 Subject: [PATCH 1/2] 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 --- .../properties/AnnotationsPropertySource.java | 12 +++- .../AnnotationsPropertySourceTests.java | 70 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) 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..60c992294e 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 @@ -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,17 @@ 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) { + for (Method attribute : ((MergedAnnotation) value).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..27edd3f723 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 @@ -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 { + + } + } From 11a153869ff2902391b804d72cc357d90e2527a3 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 27 Oct 2020 18:49:04 -0700 Subject: [PATCH 2/2] Polish 'Support nested @PropertyMapping annotations' See gh-23146 --- .../properties/AnnotationsPropertySource.java | 9 +++++---- .../properties/AnnotationsPropertySourceTests.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) 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 60c992294e..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; } @@ -127,7 +127,8 @@ public class AnnotationsPropertySource extends EnumerablePropertySource } } else if (value instanceof MergedAnnotation) { - for (Method attribute : ((MergedAnnotation) value).getType().getDeclaredMethods()) { + MergedAnnotation annotation = (MergedAnnotation) value; + for (Method attribute : annotation.getType().getDeclaredMethods()) { collectProperties(name, defaultSkip, (MergedAnnotation) value, attribute, properties); } } 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 27edd3f723..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.