Add @NestedConfigurationProperty meta-data support

Add a @NestedConfigurationProperty annotation which can be used to
customize how configuration mete-data is generated.

Prior to this commit only inner-classes where considered nested
(see Tomcat in ServerProperties). Using this new annotation, the Ssl
property in ServerProperties can be detected as well.

See gh-1001
pull/1815/head
Stephane Nicoll 10 years ago committed by Phillip Webb
parent fbf8f56a97
commit a46396f691

@ -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.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -60,6 +61,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer {
private String contextPath; private String contextPath;
@NestedConfigurationProperty
private Ssl ssl; private Ssl ssl;
@NotNull @NotNull

@ -64,6 +64,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot." static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot."
+ "context.properties.ConfigurationProperties"; + "context.properties.ConfigurationProperties";
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
+ "context.properties.NestedConfigurationProperty";
private ConfigurationMetadata metadata; private ConfigurationMetadata metadata;
private TypeUtils typeUtils; private TypeUtils typeUtils;
@ -74,6 +77,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return CONFIGURATION_PROPERTIES_ANNOTATION; return CONFIGURATION_PROPERTIES_ANNOTATION;
} }
protected String nestedConfigurationPropertyAnnotation() {
return NESTED_CONFIGURATION_PROPERTY_ANNOTATION;
}
@Override @Override
public synchronized void init(ProcessingEnvironment env) { public synchronized void init(ProcessingEnvironment env) {
super.init(env); super.init(env);
@ -166,8 +173,11 @@ 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);
if (setter != null boolean isNested = getAnnotation(field,
|| this.typeUtils.isCollectionOrMap(getter.getReturnType())) { nestedConfigurationPropertyAnnotation()) != null;
boolean isCollection = this.typeUtils.isCollectionOrMap(getter
.getReturnType());
if (!isNested && (setter != null || isCollection)) {
String dataType = this.typeUtils.getType(getter.getReturnType()); String dataType = this.typeUtils.getType(getter.getReturnType());
String sourceType = this.typeUtils.getType(element); String sourceType = this.typeUtils.getType(element);
String description = this.typeUtils.getJavaDoc(field); String description = this.typeUtils.getJavaDoc(field);
@ -182,17 +192,21 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
TypeElementMembers members) { TypeElementMembers members) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters() for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
.entrySet()) { .entrySet()) {
String name = entry.getKey();
ExecutableElement getter = entry.getValue(); ExecutableElement getter = entry.getValue();
VariableElement field = members.getFields().get(name);
Element returnType = this.processingEnv.getTypeUtils().asElement( Element returnType = this.processingEnv.getTypeUtils().asElement(
getter.getReturnType()); getter.getReturnType());
AnnotationMirror annotation = getAnnotation(getter, AnnotationMirror annotation = getAnnotation(getter,
configurationPropertiesAnnotation()); configurationPropertiesAnnotation());
boolean isNested = getAnnotation(field,
nestedConfigurationPropertyAnnotation()) != null;
if (returnType != null && returnType instanceof TypeElement if (returnType != null && returnType instanceof TypeElement
&& annotation == null) { && annotation == null) {
TypeElement returns = (TypeElement) returnType; TypeElement returns = (TypeElement) returnType;
if (this.typeUtils.isEnclosedIn(returnType, element)) { if (this.typeUtils.isEnclosedIn(returnType, element) || isNested) {
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, String nestedPrefix = ConfigurationMetadata
entry.getKey()); .nestedPrefix(prefix, name);
this.metadata.add(ItemMetadata.newGroup(nestedPrefix, this.metadata.add(ItemMetadata.newGroup(nestedPrefix,
this.typeUtils.getType(returns), this.typeUtils.getType(returns),
this.typeUtils.getType(element), getter.toString())); this.typeUtils.getType(element), getter.toString()));
@ -203,11 +217,13 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
} }
private AnnotationMirror getAnnotation(Element element, String type) { private AnnotationMirror getAnnotation(Element element, String type) {
if (element != null) {
for (AnnotationMirror annotation : element.getAnnotationMirrors()) { for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
if (type.equals(annotation.getAnnotationType().toString())) { if (type.equals(annotation.getAnnotationType().toString())) {
return annotation; return annotation;
} }
} }
}
return null; return null;
} }

@ -39,6 +39,7 @@ import org.springframework.boot.configurationsample.simple.SimpleTypeProperties;
import org.springframework.boot.configurationsample.specific.InnerClassAnnotatedGetterConfig; import org.springframework.boot.configurationsample.specific.InnerClassAnnotatedGetterConfig;
import org.springframework.boot.configurationsample.specific.InnerClassProperties; import org.springframework.boot.configurationsample.specific.InnerClassProperties;
import org.springframework.boot.configurationsample.specific.InnerClassRootConfig; 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.equalTo;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -238,11 +239,8 @@ public class ConfigurationMetadataAnnotationProcessorTests {
.fromSource(InnerClassProperties.class)); .fromSource(InnerClassProperties.class));
assertThat(metadata, containsProperty("config.the-second.name")); assertThat(metadata, containsProperty("config.the-second.name"));
assertThat(metadata, containsProperty("config.the-second.bar.name")); assertThat(metadata, containsProperty("config.the-second.bar.name"));
assertThat( assertThat(metadata, containsGroup("config.third").ofType(SimplePojo.class)
metadata, .fromSource(InnerClassProperties.class));
containsGroup("config.third").ofType(
InnerClassProperties.SimplePojo.class).fromSource(
InnerClassProperties.class));
assertThat(metadata, containsProperty("config.third.value")); 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 CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationProperties";
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.NestedConfigurationProperty";
private ConfigurationMetadata metadata; private ConfigurationMetadata metadata;
@Override @Override
@ -274,6 +274,11 @@ public class ConfigurationMetadataAnnotationProcessorTests {
return CONFIGURATION_PROPERTIES_ANNOTATION; return CONFIGURATION_PROPERTIES_ANNOTATION;
} }
@Override
protected String nestedConfigurationPropertyAnnotation() {
return NESTED_CONFIGURATION_PROPERTY_ANNOTATION;
}
@Override @Override
protected void writeMetaData(ConfigurationMetadata metadata) { protected void writeMetaData(ConfigurationMetadata metadata) {
this.metadata = metadata; this.metadata = metadata;

@ -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 {
}

@ -17,6 +17,7 @@
package org.springframework.boot.configurationsample.specific; package org.springframework.boot.configurationsample.specific;
import org.springframework.boot.configurationsample.ConfigurationProperties; import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
/** /**
* Demonstrate the auto-detection of a inner config classes. * Demonstrate the auto-detection of a inner config classes.
@ -30,6 +31,7 @@ public class InnerClassProperties {
private Foo second = new Foo(); private Foo second = new Foo();
@NestedConfigurationProperty
private final SimplePojo third = new SimplePojo(); private final SimplePojo third = new SimplePojo();
public Foo getFirst() { public Foo getFirst() {
@ -81,9 +83,4 @@ public class InnerClassProperties {
} }
public static class SimplePojo extends
org.springframework.boot.configurationsample.specific.SimplePojo {
}
} }

@ -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 {
}
Loading…
Cancel
Save