From d8b1f1692a46f27c91307bafe2a76fc6ea1d86d4 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 17 Jan 2018 10:45:10 +0100 Subject: [PATCH] Fix type detection with annotated getter This commit makes sure that the `type` of a property is generated property if the getter of the property is annotated. Previously, a type implementation may expose the annotation information. Closes gh-11512 --- spring-boot-integration-tests/pom.xml | 1 + .../pom.xml | 49 +++++++++ .../java/com/example/AnnotatedSample.java | 45 ++++++++ ...onfigurationProcessorIntegrationTests.java | 62 +++++++++++ .../configurationprocessor/TypeUtils.java | 100 ++++++++++++------ 5 files changed, 222 insertions(+), 35 deletions(-) create mode 100644 spring-boot-integration-tests/spring-boot-configuration-processor-tests/pom.xml create mode 100644 spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/main/java/com/example/AnnotatedSample.java create mode 100644 spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/test/java/org/springframework/boot/configurationprocessor/tests/ConfigurationProcessorIntegrationTests.java diff --git a/spring-boot-integration-tests/pom.xml b/spring-boot-integration-tests/pom.xml index e9938802ff..83035f030a 100644 --- a/spring-boot-integration-tests/pom.xml +++ b/spring-boot-integration-tests/pom.xml @@ -21,6 +21,7 @@ 1.8 + spring-boot-configuration-processor-tests spring-boot-devtools-tests spring-boot-integration-tests-embedded-servlet-container spring-boot-gradle-tests diff --git a/spring-boot-integration-tests/spring-boot-configuration-processor-tests/pom.xml b/spring-boot-integration-tests/spring-boot-configuration-processor-tests/pom.xml new file mode 100644 index 0000000000..b53545a47d --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-configuration-processor-tests/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-integration-tests + 1.5.10.BUILD-SNAPSHOT + + spring-boot-configuration-processor-tests + Spring Boot Configuration Processor Tests + ${project.name} + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot + + + org.springframework.boot + spring-boot-configuration-metadata + + + javax.validation + validation-api + + 2.0.1.Final + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/main/java/com/example/AnnotatedSample.java b/spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/main/java/com/example/AnnotatedSample.java new file mode 100644 index 0000000000..cd13018e8c --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/main/java/com/example/AnnotatedSample.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2018 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 com.example; + +import javax.validation.Valid; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Test that a valid type is generated if an annotation is present. + * + * @author Stephane Nicoll + */ +@ConfigurationProperties("annotated") +public class AnnotatedSample { + + /** + * A valid name. + */ + private String name; + + @Valid + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/test/java/org/springframework/boot/configurationprocessor/tests/ConfigurationProcessorIntegrationTests.java b/spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/test/java/org/springframework/boot/configurationprocessor/tests/ConfigurationProcessorIntegrationTests.java new file mode 100644 index 0000000000..8c65e06790 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-configuration-processor-tests/src/test/java/org/springframework/boot/configurationprocessor/tests/ConfigurationProcessorIntegrationTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2018 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.configurationprocessor.tests; + +import java.io.IOException; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for the configuration metadata annotation processor. + * + * @author Stephane Nicoll + */ +public class ConfigurationProcessorIntegrationTests { + + private static ConfigurationMetadataRepository repository; + + @BeforeClass + public static void readMetadata() throws IOException { + Resource resource = new ClassPathResource( + "META-INF/spring-configuration-metadata.json"); + assertThat(resource.exists()).isTrue(); + // Make sure the right file is detected + assertThat(resource.getURL().toString()).contains( + "spring-boot-configuration-processor-tests"); + repository = ConfigurationMetadataRepositoryJsonBuilder + .create(resource.getInputStream()).build(); + + } + + @Test + public void extractTypeFromAnnotatedGetter() { + ConfigurationMetadataProperty property = repository.getAllProperties().get( + "annotated.name"); + assertThat(property).isNotNull(); + assertThat(property.getType()).isEqualTo("java.lang.String"); + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java index 0fd0c7479b..8bf3cb28ee 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java @@ -25,8 +25,10 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; /** @@ -65,6 +67,8 @@ class TypeUtils { private final ProcessingEnvironment env; + private final TypeExtractor typeExtractor; + private final TypeMirror collectionType; private final TypeMirror mapType; @@ -72,6 +76,7 @@ class TypeUtils { TypeUtils(ProcessingEnvironment env) { this.env = env; Types types = env.getTypeUtils(); + this.typeExtractor = new TypeExtractor(types); this.collectionType = getDeclaredType(types, Collection.class, 1); this.mapType = getDeclaredType(types, Map.class, 2); } @@ -100,20 +105,7 @@ class TypeUtils { * {@link Class#forName(String)} */ public String getQualifiedName(Element element) { - if (element == null) { - return null; - } - TypeElement enclosingElement = getEnclosingTypeElement(element.asType()); - if (enclosingElement != null) { - return getQualifiedName(enclosingElement) + "$" - + ((DeclaredType) element.asType()).asElement().getSimpleName() - .toString(); - } - if (element instanceof TypeElement) { - return ((TypeElement) element).getQualifiedName().toString(); - } - throw new IllegalStateException( - "Could not extract qualified name from " + element); + return this.typeExtractor.getQualifiedName(element); } /** @@ -126,27 +118,7 @@ class TypeUtils { if (type == null) { return null; } - Class wrapper = getWrapperFor(type); - if (wrapper != null) { - return wrapper.getName(); - } - TypeElement enclosingElement = getEnclosingTypeElement(type); - if (enclosingElement != null) { - return getQualifiedName(enclosingElement) + "$" - + ((DeclaredType) type).asElement().getSimpleName().toString(); - } - return type.toString(); - } - - private TypeElement getEnclosingTypeElement(TypeMirror type) { - if (type instanceof DeclaredType) { - DeclaredType declaredType = (DeclaredType) type; - Element enclosingElement = declaredType.asElement().getEnclosingElement(); - if (enclosingElement != null && enclosingElement instanceof TypeElement) { - return (TypeElement) enclosingElement; - } - } - return null; + return type.accept(this.typeExtractor, null); } public boolean isCollectionOrMap(TypeMirror type) { @@ -194,4 +166,62 @@ class TypeUtils { return WRAPPER_TO_PRIMITIVE.get(type.toString()); } + + /** + * A visitor that extracts the full qualified name of a type, including generic + * information. + */ + private static class TypeExtractor extends SimpleTypeVisitor6 { + + private final Types types; + + TypeExtractor(Types types) { + this.types = types; + } + + @Override + public String visitDeclared(DeclaredType type, Void none) { + TypeElement enclosingElement = getEnclosingTypeElement(type); + if (enclosingElement != null) { + return getQualifiedName(enclosingElement) + "$" + + type.asElement().getSimpleName().toString(); + } + return type.toString(); + } + + @Override + public String visitPrimitive(PrimitiveType t, Void none) { + return this.types.boxedClass(t).getQualifiedName().toString(); + } + + public String getQualifiedName(Element element) { + if (element == null) { + return null; + } + TypeElement enclosingElement = getEnclosingTypeElement(element.asType()); + if (enclosingElement != null) { + return getQualifiedName(enclosingElement) + "$" + + ((DeclaredType) element.asType()).asElement().getSimpleName() + .toString(); + } + if (element instanceof TypeElement) { + return ((TypeElement) element).getQualifiedName().toString(); + } + throw new IllegalStateException( + "Could not extract qualified name from " + element); + } + + private TypeElement getEnclosingTypeElement(TypeMirror type) { + if (type instanceof DeclaredType) { + DeclaredType declaredType = (DeclaredType) type; + Element enclosingElement = declaredType.asElement().getEnclosingElement(); + if (enclosingElement != null && enclosingElement instanceof TypeElement) { + return (TypeElement) enclosingElement; + } + } + return null; + } + + } + }