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.
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]]
......
......@@ -191,6 +191,11 @@
<artifactId>json</artifactId>
<version>${json.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.12.6</version>
</dependency>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-zip</artifactId>
......
......@@ -23,6 +23,12 @@
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
......
......@@ -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<String, Object> 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<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,
TypeElementMembers members) {
for (Map.Entry<String, ExecutableElement> 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) {
......
......@@ -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 {
......
/*
* 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