diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 5a1a82c17b..f8d5a0335c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -38,6 +38,7 @@ import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomize import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.util.StringUtils; /** @@ -60,6 +61,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { private String contextPath; + @NestedConfigurationProperty private Ssl ssl; @NotNull 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 f2a08639d4..371bbd2ad1 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 @@ -64,6 +64,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot." + "context.properties.ConfigurationProperties"; + static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot." + + "context.properties.NestedConfigurationProperty"; + private ConfigurationMetadata metadata; private TypeUtils typeUtils; @@ -74,6 +77,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor return CONFIGURATION_PROPERTIES_ANNOTATION; } + protected String nestedConfigurationPropertyAnnotation() { + return NESTED_CONFIGURATION_PROPERTY_ANNOTATION; + } + @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); @@ -166,8 +173,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ExecutableElement getter = entry.getValue(); ExecutableElement setter = members.getPublicSetters().get(name); VariableElement field = members.getFields().get(name); - if (setter != null - || this.typeUtils.isCollectionOrMap(getter.getReturnType())) { + boolean isNested = getAnnotation(field, + nestedConfigurationPropertyAnnotation()) != null; + boolean isCollection = this.typeUtils.isCollectionOrMap(getter + .getReturnType()); + if (!isNested && (setter != null || isCollection)) { String dataType = this.typeUtils.getType(getter.getReturnType()); String sourceType = this.typeUtils.getType(element); String description = this.typeUtils.getJavaDoc(field); @@ -182,17 +192,21 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor TypeElementMembers members) { for (Map.Entry entry : members.getPublicGetters() .entrySet()) { + String name = entry.getKey(); ExecutableElement getter = entry.getValue(); + VariableElement field = members.getFields().get(name); Element returnType = this.processingEnv.getTypeUtils().asElement( getter.getReturnType()); AnnotationMirror annotation = getAnnotation(getter, configurationPropertiesAnnotation()); + boolean isNested = getAnnotation(field, + nestedConfigurationPropertyAnnotation()) != null; if (returnType != null && returnType instanceof TypeElement && annotation == null) { TypeElement returns = (TypeElement) returnType; - if (this.typeUtils.isEnclosedIn(returnType, element)) { - String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, - entry.getKey()); + if (this.typeUtils.isEnclosedIn(returnType, element) || isNested) { + String nestedPrefix = ConfigurationMetadata + .nestedPrefix(prefix, name); this.metadata.add(ItemMetadata.newGroup(nestedPrefix, this.typeUtils.getType(returns), this.typeUtils.getType(element), getter.toString())); @@ -203,9 +217,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor } private AnnotationMirror getAnnotation(Element element, String type) { - for (AnnotationMirror annotation : element.getAnnotationMirrors()) { - if (type.equals(annotation.getAnnotationType().toString())) { - return annotation; + if (element != null) { + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + if (type.equals(annotation.getAnnotationType().toString())) { + return annotation; + } } } return null; 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 023692e7e1..e4d8ffb0de 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 @@ -39,6 +39,7 @@ import org.springframework.boot.configurationsample.simple.SimpleTypeProperties; import org.springframework.boot.configurationsample.specific.InnerClassAnnotatedGetterConfig; import org.springframework.boot.configurationsample.specific.InnerClassProperties; import org.springframework.boot.configurationsample.specific.InnerClassRootConfig; +import org.springframework.boot.configurationsample.specific.SimplePojo; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; @@ -238,11 +239,8 @@ public class ConfigurationMetadataAnnotationProcessorTests { .fromSource(InnerClassProperties.class)); assertThat(metadata, containsProperty("config.the-second.name")); assertThat(metadata, containsProperty("config.the-second.bar.name")); - assertThat( - metadata, - containsGroup("config.third").ofType( - InnerClassProperties.SimplePojo.class).fromSource( - InnerClassProperties.class)); + assertThat(metadata, containsGroup("config.third").ofType(SimplePojo.class) + .fromSource(InnerClassProperties.class)); assertThat(metadata, containsProperty("config.third.value")); } @@ -267,6 +265,8 @@ public class ConfigurationMetadataAnnotationProcessorTests { static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationProperties"; + static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.NestedConfigurationProperty"; + private ConfigurationMetadata metadata; @Override @@ -274,6 +274,11 @@ public class ConfigurationMetadataAnnotationProcessorTests { return CONFIGURATION_PROPERTIES_ANNOTATION; } + @Override + protected String nestedConfigurationPropertyAnnotation() { + return NESTED_CONFIGURATION_PROPERTY_ANNOTATION; + } + @Override protected void writeMetaData(ConfigurationMetadata metadata) { this.metadata = metadata; diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/NestedConfigurationProperty.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/NestedConfigurationProperty.java new file mode 100644 index 0000000000..8d2457bff0 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/NestedConfigurationProperty.java @@ -0,0 +1,38 @@ +/* + * 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Alternative to Spring Boot's {@code @NestedConfigurationProperty} for testing (removes + * the need for a dependency on the real annotation). + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 1.2.0 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NestedConfigurationProperty { + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java index d9791a7571..ff03fb68f5 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InnerClassProperties.java @@ -17,6 +17,7 @@ package org.springframework.boot.configurationsample.specific; import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.NestedConfigurationProperty; /** * Demonstrate the auto-detection of a inner config classes. @@ -30,6 +31,7 @@ public class InnerClassProperties { private Foo second = new Foo(); + @NestedConfigurationProperty private final SimplePojo third = new SimplePojo(); public Foo getFirst() { @@ -81,9 +83,4 @@ public class InnerClassProperties { } - public static class SimplePojo extends - org.springframework.boot.configurationsample.specific.SimplePojo { - - } - } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/NestedConfigurationProperty.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/NestedConfigurationProperty.java new file mode 100644 index 0000000000..ebb78b3491 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/NestedConfigurationProperty.java @@ -0,0 +1,40 @@ +/* + * 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.context.properties; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a field in a {@link ConfigurationProperties} object should be treated as + * if it were a nested type. This annotation has no bearing on the actual binding + * processes, but it is used by the {@code spring-boot-configuration-processor} as a hint + * that a field is not bound as a single value. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 1.2.0 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NestedConfigurationProperty { + +}