Commit 77427f53 authored by Stephane Nicoll's avatar Stephane Nicoll

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
parent 82c61421
...@@ -190,6 +190,10 @@ will be used to populate the `description` attribute. ...@@ -190,6 +190,10 @@ will be used to populate the `description` attribute.
NOTE: You should only use simple text with `@ConfigurationProperties` field Javadoc since NOTE: You should only use simple text with `@ConfigurationProperties` field Javadoc since
they are not processed before being added to the JSON. 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]] [[configuration-metadata-nested-properties]]
......
...@@ -191,6 +191,11 @@ ...@@ -191,6 +191,11 @@
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>${json.version}</version> <version>${json.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.12.6</version>
</dependency>
<dependency> <dependency>
<groupId>org.zeroturnaround</groupId> <groupId>org.zeroturnaround</groupId>
<artifactId>zt-zip</artifactId> <artifactId>zt-zip</artifactId>
......
...@@ -23,6 +23,12 @@ ...@@ -23,6 +23,12 @@
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
......
...@@ -40,6 +40,7 @@ import javax.lang.model.element.Modifier; ...@@ -40,6 +40,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject; import javax.tools.FileObject;
...@@ -69,6 +70,12 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -69,6 +70,12 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot." static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
+ "context.properties.NestedConfigurationProperty"; + "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 ConfigurationMetadata metadata;
private TypeUtils typeUtils; private TypeUtils typeUtils;
...@@ -157,6 +164,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -157,6 +164,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
TypeElementMembers members = new TypeElementMembers(this.processingEnv, element); TypeElementMembers members = new TypeElementMembers(this.processingEnv, element);
Map<String, Object> fieldValues = getFieldValues(element); Map<String, Object> fieldValues = getFieldValues(element);
processSimpleTypes(prefix, element, members, fieldValues); processSimpleTypes(prefix, element, members, fieldValues);
processLombokTypes(prefix, element, members, fieldValues);
processNestedTypes(prefix, element, members); processNestedTypes(prefix, element, members);
} }
...@@ -177,15 +185,14 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -177,15 +185,14 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
ExecutableElement getter = entry.getValue(); ExecutableElement getter = entry.getValue();
ExecutableElement setter = members.getPublicSetters().get(name); ExecutableElement setter = members.getPublicSetters().get(name);
VariableElement field = members.getFields().get(name); VariableElement field = members.getFields().get(name);
Element returnType = this.processingEnv.getTypeUtils().asElement( TypeMirror returnType = getter.getReturnType();
getter.getReturnType()); Element returnTypeElement = this.processingEnv.getTypeUtils().asElement(
boolean isExcluded = this.typeExcludeFilter.isExcluded(getter returnType);
.getReturnType()); boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType);
boolean isNested = isNested(returnType, field, element); boolean isNested = isNested(returnTypeElement, field, element);
boolean isCollection = this.typeUtils.isCollectionOrMap(getter boolean isCollection = this.typeUtils.isCollectionOrMap(returnType);
.getReturnType());
if (!isExcluded && !isNested && (setter != null || isCollection)) { 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 sourceType = this.typeUtils.getType(element);
String description = this.typeUtils.getJavaDoc(field); String description = this.typeUtils.getJavaDoc(field);
Object defaultValue = fieldValues.get(name); Object defaultValue = fieldValues.get(name);
...@@ -198,6 +205,48 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -198,6 +205,48 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
} }
} }
private void processLombokTypes(String prefix, TypeElement element,
TypeElementMembers members, Map<String, Object> fieldValues) {
for (Map.Entry<String, VariableElement> 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, private void processNestedTypes(String prefix, TypeElement element,
TypeElementMembers members) { TypeElementMembers members) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters() for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
...@@ -223,7 +272,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -223,7 +272,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private boolean isNested(Element returnType, VariableElement field, private boolean isNested(Element returnType, VariableElement field,
TypeElement element) { TypeElement element) {
if (getAnnotation(field, nestedConfigurationPropertyAnnotation()) != null) { if (hasAnnotation(field, nestedConfigurationPropertyAnnotation())) {
return true; return true;
} }
return this.typeUtils.isEnclosedIn(returnType, element) return this.typeUtils.isEnclosedIn(returnType, element)
...@@ -231,7 +280,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -231,7 +280,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
} }
private boolean hasDeprecateAnnotation(Element element) { 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) { private AnnotationMirror getAnnotation(Element element, String type) {
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.IOException; import java.io.IOException;
import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
...@@ -25,7 +24,11 @@ import javax.lang.model.SourceVersion; ...@@ -25,7 +24,11 @@ import javax.lang.model.SourceVersion;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; 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.EmptyTypeMethodConfig;
import org.springframework.boot.configurationsample.method.InvalidMethodConfig; import org.springframework.boot.configurationsample.method.InvalidMethodConfig;
import org.springframework.boot.configurationsample.method.MethodAndClassConfig; import org.springframework.boot.configurationsample.method.MethodAndClassConfig;
...@@ -286,13 +289,49 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -286,13 +289,49 @@ public class ConfigurationMetadataAnnotationProcessorTests {
assertThat(metadata, not(containsProperty("excluded.writer-array"))); 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 { private ConfigurationMetadata compile(Class<?>... types) throws IOException {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor(); TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor();
new TestCompiler(this.temporaryFolder).getTask(types).call(processor); new TestCompiler(this.temporaryFolder).getTask(types).call(processor);
return processor.getMetadata(); return processor.getMetadata();
} }
@SupportedAnnotationTypes({ "*" }) @SupportedAnnotationTypes({"*"})
@SupportedSourceVersion(SourceVersion.RELEASE_6) @SupportedSourceVersion(SourceVersion.RELEASE_6)
private static class TestConfigurationMetadataAnnotationProcessor extends private static class TestConfigurationMetadataAnnotationProcessor extends
ConfigurationMetadataAnnotationProcessor { ConfigurationMetadataAnnotationProcessor {
......
/*
* 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<String> items = new ArrayList<String>();
// Should be ignored if no annotation is set
private String ignored;
}
/*
* 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<String> items = new ArrayList<String>();
private final String ignored = "foo";
}
/*
* 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<String> items = new ArrayList<String>();
private final String ignored = "foo";
}
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