From 77427f53ccddec94ca2dfb34562022a4e1636c8e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 11 Dec 2014 17:51:26 +0100 Subject: [PATCH] Support of Lombok annotated ConfigurationProperties Previously, no configuration properties were discovered on a class using lombok instead of regular getters/setters. This commit adds a support for some of the lombok annotations, specifically that is @Data, @Getter and @Setter. Provides the same semantic as what lombok is generating. Closes gh-2114 --- .../appendix-configuration-metadata.adoc | 4 + spring-boot-parent/pom.xml | 5 ++ .../pom.xml | 6 ++ ...figurationMetadataAnnotationProcessor.java | 73 ++++++++++++++++--- ...ationMetadataAnnotationProcessorTests.java | 43 ++++++++++- .../lombok/LombokExplicitProperties.java | 59 +++++++++++++++ .../lombok/LombokSimpleDataProperties.java | 53 ++++++++++++++ .../lombok/LombokSimpleProperties.java | 55 ++++++++++++++ 8 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokExplicitProperties.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleDataProperties.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleProperties.java diff --git a/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc b/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc index 78ac6162831..bef5495654a 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc @@ -190,6 +190,10 @@ will be used to populate the `description` attribute. NOTE: You should only use simple text with `@ConfigurationProperties` field Javadoc since they are not processed before being added to the JSON. +Properties are discovered via the presence of standard getters and setters with special +handling for collection types (that will be detected even if only a getter is present). The +annotation processor also supports the use of the `@Data`, `@Getter` and `@Setter` lombok +annotations. [[configuration-metadata-nested-properties]] diff --git a/spring-boot-parent/pom.xml b/spring-boot-parent/pom.xml index d60e2b4866f..1c6d221bcf8 100644 --- a/spring-boot-parent/pom.xml +++ b/spring-boot-parent/pom.xml @@ -191,6 +191,11 @@ json ${json.version} + + org.projectlombok + lombok + 1.12.6 + org.zeroturnaround zt-zip diff --git a/spring-boot-tools/spring-boot-configuration-processor/pom.xml b/spring-boot-tools/spring-boot-configuration-processor/pom.xml index 49deddfa00e..3d2877d51d3 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/pom.xml +++ b/spring-boot-tools/spring-boot-configuration-processor/pom.xml @@ -23,6 +23,12 @@ org.json json + + + org.projectlombok + lombok + test + diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 54347446e3b..85d153247ca 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -40,6 +40,7 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; @@ -69,6 +70,12 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot." + "context.properties.NestedConfigurationProperty"; + static final String LOMBOK_DATA_ANNOTATION = "lombok.Data"; + + static final String LOMBOK_GETTER_ANNOTATION = "lombok.Getter"; + + static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter"; + private ConfigurationMetadata metadata; private TypeUtils typeUtils; @@ -157,6 +164,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor TypeElementMembers members = new TypeElementMembers(this.processingEnv, element); Map fieldValues = getFieldValues(element); processSimpleTypes(prefix, element, members, fieldValues); + processLombokTypes(prefix, element, members, fieldValues); processNestedTypes(prefix, element, members); } @@ -177,15 +185,14 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ExecutableElement getter = entry.getValue(); ExecutableElement setter = members.getPublicSetters().get(name); VariableElement field = members.getFields().get(name); - Element returnType = this.processingEnv.getTypeUtils().asElement( - getter.getReturnType()); - boolean isExcluded = this.typeExcludeFilter.isExcluded(getter - .getReturnType()); - boolean isNested = isNested(returnType, field, element); - boolean isCollection = this.typeUtils.isCollectionOrMap(getter - .getReturnType()); + TypeMirror returnType = getter.getReturnType(); + Element returnTypeElement = this.processingEnv.getTypeUtils().asElement( + returnType); + boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType); + boolean isNested = isNested(returnTypeElement, field, element); + boolean isCollection = this.typeUtils.isCollectionOrMap(returnType); if (!isExcluded && !isNested && (setter != null || isCollection)) { - String dataType = this.typeUtils.getType(getter.getReturnType()); + String dataType = this.typeUtils.getType(returnType); String sourceType = this.typeUtils.getType(element); String description = this.typeUtils.getJavaDoc(field); Object defaultValue = fieldValues.get(name); @@ -198,6 +205,48 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor } } + private void processLombokTypes(String prefix, TypeElement element, + TypeElementMembers members, Map fieldValues) { + for (Map.Entry entry : members.getFields() + .entrySet()) { + String name = entry.getKey(); + VariableElement field = entry.getValue(); + if (!isLombokField(field, element)) { + continue; + } + TypeMirror returnType = field.asType(); + Element returnTypeElement = this.processingEnv.getTypeUtils().asElement( + returnType); + boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType); + boolean isNested = isNested(returnTypeElement, field, element); + boolean isCollection = this.typeUtils.isCollectionOrMap(returnType); + boolean hasSetter = hasLombokSetter(field, element); + if (!isExcluded && !isNested && (hasSetter || isCollection)) { + String dataType = this.typeUtils.getType(returnType); + String sourceType = this.typeUtils.getType(element); + String description = this.typeUtils.getJavaDoc(field); + Object defaultValue = fieldValues.get(name); + boolean deprecated = hasDeprecateAnnotation(field) + || hasDeprecateAnnotation(element); + this.metadata.add(ItemMetadata.newProperty(prefix, name, dataType, + sourceType, null, description, defaultValue, deprecated)); + } + } + } + + private boolean isLombokField(VariableElement field, TypeElement element) { + return hasAnnotation(field, LOMBOK_GETTER_ANNOTATION) + || hasAnnotation(element, LOMBOK_GETTER_ANNOTATION) + || hasAnnotation(element, LOMBOK_DATA_ANNOTATION); + } + + private boolean hasLombokSetter(VariableElement field, TypeElement element) { + return !field.getModifiers().contains(Modifier.FINAL) && ( + hasAnnotation(field, LOMBOK_SETTER_ANNOTATION) + || hasAnnotation(element, LOMBOK_SETTER_ANNOTATION) + || hasAnnotation(element, LOMBOK_DATA_ANNOTATION)); + } + private void processNestedTypes(String prefix, TypeElement element, TypeElementMembers members) { for (Map.Entry entry : members.getPublicGetters() @@ -223,7 +272,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor private boolean isNested(Element returnType, VariableElement field, TypeElement element) { - if (getAnnotation(field, nestedConfigurationPropertyAnnotation()) != null) { + if (hasAnnotation(field, nestedConfigurationPropertyAnnotation())) { return true; } return this.typeUtils.isEnclosedIn(returnType, element) @@ -231,7 +280,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor } private boolean hasDeprecateAnnotation(Element element) { - return getAnnotation(element, "java.lang.Deprecated") != null; + return hasAnnotation(element, "java.lang.Deprecated"); + } + + private boolean hasAnnotation(Element element, String type) { + return getAnnotation(element, type) != null; } private AnnotationMirror getAnnotation(Element element, String type) { diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 58479cbe705..8a136381eb6 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.configurationprocessor; import java.io.IOException; - import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; @@ -25,7 +24,11 @@ import javax.lang.model.SourceVersion; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; + import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties; +import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties; +import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties; import org.springframework.boot.configurationsample.method.EmptyTypeMethodConfig; import org.springframework.boot.configurationsample.method.InvalidMethodConfig; import org.springframework.boot.configurationsample.method.MethodAndClassConfig; @@ -286,13 +289,49 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata, not(containsProperty("excluded.writer-array"))); } + @Test + public void lombokDataProperties() throws Exception { + ConfigurationMetadata metadata = compile(LombokSimpleDataProperties.class); + assertSimpleLombokProperties(metadata, LombokSimpleDataProperties.class, "data"); + } + + @Test + public void lombokSimpleProperties() throws Exception { + ConfigurationMetadata metadata = compile(LombokSimpleProperties.class); + assertSimpleLombokProperties(metadata, LombokSimpleProperties.class, "simple"); + } + + @Test + public void lombokExplicitProperties() throws Exception { + ConfigurationMetadata metadata = compile(LombokExplicitProperties.class); + assertSimpleLombokProperties(metadata, LombokExplicitProperties.class, "explicit"); + } + + private void assertSimpleLombokProperties(ConfigurationMetadata metadata, Class source, String prefix) { + assertThat(metadata, containsGroup(prefix).fromSource(source)); + assertThat(metadata, not(containsProperty(prefix + ".id"))); + assertThat( + metadata, + containsProperty(prefix + ".name", String.class) + .fromSource(source) + .withDescription("Name description.")); + assertThat(metadata, containsProperty(prefix + ".description")); + assertThat(metadata, containsProperty(prefix + ".counter")); + assertThat(metadata, + containsProperty(prefix + ".number").fromSource(source) + .withDefaultValue(is(0)) + .withDeprecated()); + assertThat(metadata, containsProperty(prefix + ".items")); + assertThat(metadata, not(containsProperty(prefix + ".ignored"))); + } + private ConfigurationMetadata compile(Class... types) throws IOException { TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor(); new TestCompiler(this.temporaryFolder).getTask(types).call(processor); return processor.getMetadata(); } - @SupportedAnnotationTypes({ "*" }) + @SupportedAnnotationTypes({"*"}) @SupportedSourceVersion(SourceVersion.RELEASE_6) private static class TestConfigurationMetadataAnnotationProcessor extends ConfigurationMetadataAnnotationProcessor { diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokExplicitProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokExplicitProperties.java new file mode 100644 index 00000000000..8ae45e6984a --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokExplicitProperties.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.lombok; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Configuration properties using lombok @Getter/@Setter at field level. + * + * @author Stephane Nicoll + */ +@ConfigurationProperties(prefix = "explicit") +public class LombokExplicitProperties { + + @Getter + private final String id = "super-id"; + + /** + * Name description. + */ + @Getter @Setter + private String name; + + @Getter @Setter + private String description; + + @Getter @Setter + private Integer counter; + + @Deprecated @Getter @Setter + private Integer number = 0; + + @Getter + private final List items = new ArrayList(); + + // Should be ignored if no annotation is set + private String ignored; + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleDataProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleDataProperties.java new file mode 100644 index 00000000000..31000b16c50 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleDataProperties.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.lombok; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Configuration properties using lombok @Data. + * + * @author Stephane Nicoll + */ +@Data +@ConfigurationProperties(prefix = "data") +public class LombokSimpleDataProperties { + + private final String id = "super-id"; + + /** + * Name description. + */ + private String name; + + private String description; + + private Integer counter; + + @Deprecated + private Integer number = 0; + + private final List items = new ArrayList(); + + private final String ignored = "foo"; + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleProperties.java new file mode 100644 index 00000000000..899a0761d8c --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokSimpleProperties.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.lombok; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Configuration properties using lombok @Getter/@Setter at class level. + * + * @author Stephane Nicoll + */ +@Getter +@Setter +@ConfigurationProperties(prefix = "simple") +public class LombokSimpleProperties { + + private final String id = "super-id"; + + /** + * Name description. + */ + private String name; + + private String description; + + private Integer counter; + + @Deprecated + private Integer number = 0; + + private final List items = new ArrayList(); + + private final String ignored = "foo"; + +}