Commit 1296b4df authored by Stephane Nicoll's avatar Stephane Nicoll

Revert "Support constructor binding on 3rd party classes"

This commit reverts the support of constructor binding on 3rd party
classes using @ImportConfigurationPropertiesBean

See gh-23172

Closes gh-23593
parent 25952584
...@@ -1242,7 +1242,7 @@ Spring Boot provides infrastructure to bind `@ConfigurationProperties` types and ...@@ -1242,7 +1242,7 @@ Spring Boot provides infrastructure to bind `@ConfigurationProperties` types and
You can either enable configuration properties on a class-by-class basis or enable configuration property scanning that works in a similar manner to component scanning. You can either enable configuration properties on a class-by-class basis or enable configuration property scanning that works in a similar manner to component scanning.
Sometimes, classes annotated with `@ConfigurationProperties` might not be suitable for scanning, for example, if you're developing your own auto-configuration or you want to enable them conditionally. Sometimes, classes annotated with `@ConfigurationProperties` might not be suitable for scanning, for example, if you're developing your own auto-configuration or you want to enable them conditionally.
In these cases, specify the list of types to process using the `@EnableConfigurationProperties` or `@ImportAsConfigurationPropertiesBean` annotations. In these cases, specify the list of types to process using the `@EnableConfigurationProperties` annotation.
This can be done on any `@Configuration` class, as shown in the following example: This can be done on any `@Configuration` class, as shown in the following example:
[source,java,indent=0] [source,java,indent=0]
...@@ -1268,7 +1268,7 @@ If you want to define specific packages to scan, you can do so as shown in the f ...@@ -1268,7 +1268,7 @@ If you want to define specific packages to scan, you can do so as shown in the f
[NOTE] [NOTE]
==== ====
When the `@ConfigurationProperties` bean is registered using configuration property scanning or via `@EnableConfigurationProperties` or `@ImportAsConfigurationPropertiesBean`, the bean has a conventional name: `<prefix>-<fqn>`, where `<prefix>` is the environment key prefix specified in the `@ConfigurationProperties` annotation and `<fqn>` is the fully qualified name of the bean. When the `@ConfigurationProperties` bean is registered using configuration property scanning or via `@EnableConfigurationProperties`, the bean has a conventional name: `<prefix>-<fqn>`, where `<prefix>` is the environment key prefix specified in the `@ConfigurationProperties` annotation and `<fqn>` is the fully qualified name of the bean.
If the annotation does not provide any prefix, only the fully qualified name of the bean is used. If the annotation does not provide any prefix, only the fully qualified name of the bean is used.
The bean name in the example above is `acme-com.example.AcmeProperties`. The bean name in the example above is `acme-com.example.AcmeProperties`.
...@@ -1336,26 +1336,12 @@ To configure a bean from the `Environment` properties, add `@ConfigurationProper ...@@ -1336,26 +1336,12 @@ To configure a bean from the `Environment` properties, add `@ConfigurationProper
---- ----
@ConfigurationProperties(prefix = "another") @ConfigurationProperties(prefix = "another")
@Bean @Bean
public ExampleItem exampleItem() { public AnotherComponent anotherComponent() {
... ...
} }
---- ----
Any JavaBean property defined with the `another` prefix is mapped onto that `ExampleItem` bean in manner similar to the preceding `AcmeProperties` example. Any JavaBean property defined with the `another` prefix is mapped onto that `AnotherComponent` bean in manner similar to the preceding `AcmeProperties` example.
If you want to use constructor binding with a third-party class, you can't use a `@Bean` method since Spring will need to create the object instance.
For those situations, you can use an `@ImportAsConfigurationPropertiesBean` annotation on your `@Configuration` or `@SpringBootApplication` class.
[source,java,indent=0]
----
@SpringBootApplication
@ImportAsConfigurationPropertiesBean(type = ExampleItem.class, prefix = "another")
public class MyApp {
...
}
----
TIP: `@ImportAsConfigurationPropertiesBean` also works for JavaBean bindings as long as the type has a single no-arg constructor
......
...@@ -22,7 +22,6 @@ import java.io.StringWriter; ...@@ -22,7 +22,6 @@ import java.io.StringWriter;
import java.time.Duration; import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -41,7 +40,6 @@ import javax.lang.model.element.Modifier; ...@@ -41,7 +40,6 @@ 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.ElementFilter; import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
...@@ -82,10 +80,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -82,10 +80,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String NAME_ANNOTATION = "org.springframework.boot.context.properties.bind.Name"; static final String NAME_ANNOTATION = "org.springframework.boot.context.properties.bind.Name";
static final String IMPORT_AS_CONFIGURATION_PROPERTIES_BEAN_ANNOATION = "org.springframework.boot.context.properties.ImportAsConfigurationPropertiesBean";
static final String IMPORT_AS_CONFIGURATION_PROPERTIES_BEANS_ANNOATION = "org.springframework.boot.context.properties.ImportAsConfigurationPropertiesBeans";
private static final Set<String> SUPPORTED_OPTIONS = Collections private static final Set<String> SUPPORTED_OPTIONS = Collections
.unmodifiableSet(Collections.singleton(ADDITIONAL_METADATA_LOCATIONS_OPTION)); .unmodifiableSet(Collections.singleton(ADDITIONAL_METADATA_LOCATIONS_OPTION));
...@@ -127,14 +121,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -127,14 +121,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return NAME_ANNOTATION; return NAME_ANNOTATION;
} }
protected String importAsConfigurationPropertiesBeanAnnotation() {
return IMPORT_AS_CONFIGURATION_PROPERTIES_BEAN_ANNOATION;
}
protected String importAsConfigurationPropertiesBeansAnnotation() {
return IMPORT_AS_CONFIGURATION_PROPERTIES_BEANS_ANNOATION;
}
@Override @Override
public SourceVersion getSupportedSourceVersion() { public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported(); return SourceVersion.latestSupported();
...@@ -153,59 +139,31 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -153,59 +139,31 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(), this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(), nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
constructorBindingAnnotation(), defaultValueAnnotation(), endpointAnnotation(), constructorBindingAnnotation(), defaultValueAnnotation(), endpointAnnotation(),
readOperationAnnotation(), nameAnnotation(), importAsConfigurationPropertiesBeanAnnotation(), readOperationAnnotation(), nameAnnotation());
importAsConfigurationPropertiesBeansAnnotation());
} }
@Override @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
this.metadataCollector.processing(roundEnv); this.metadataCollector.processing(roundEnv);
processConfigurationProperties(roundEnv);
processEndpoint(roundEnv);
processImportAsConfigurationProperties(roundEnv);
if (roundEnv.processingOver()) {
try {
writeMetaData();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to write metadata", ex);
}
}
return false;
}
private void processConfigurationProperties(RoundEnvironment roundEnv) {
TypeElement annotationType = this.metadataEnv.getConfigurationPropertiesAnnotationElement(); TypeElement annotationType = this.metadataEnv.getConfigurationPropertiesAnnotationElement();
if (annotationType != null) { if (annotationType != null) { // Is @ConfigurationProperties available
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) { for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
processElement(element); processElement(element);
} }
} }
}
private void processEndpoint(RoundEnvironment roundEnv) {
TypeElement endpointType = this.metadataEnv.getEndpointAnnotationElement(); TypeElement endpointType = this.metadataEnv.getEndpointAnnotationElement();
if (endpointType != null) { if (endpointType != null) { // Is @Endpoint available
getElementsAnnotatedOrMetaAnnotatedWith(roundEnv, endpointType).forEach(this::processEndpoint); getElementsAnnotatedOrMetaAnnotatedWith(roundEnv, endpointType).forEach(this::processEndpoint);
} }
} if (roundEnv.processingOver()) {
try {
private void processImportAsConfigurationProperties(RoundEnvironment roundEnv) { writeMetaData();
TypeElement importAsConfigurationPropertiesBeanType = this.metadataEnv }
.getImportAsConfigurationPropertiesBeansAnnotation(); catch (Exception ex) {
TypeElement importAsConfigurationPropertiesBeansType = this.metadataEnv throw new IllegalStateException("Failed to write metadata", ex);
.getImportAsConfigurationPropertiesBeansAnnotationElement(); }
if (importAsConfigurationPropertiesBeanType == null && importAsConfigurationPropertiesBeansType == null) {
return;
}
Set<Element> elements = new LinkedHashSet<>();
if (importAsConfigurationPropertiesBeanType != null) {
elements.addAll(roundEnv.getElementsAnnotatedWith(importAsConfigurationPropertiesBeanType));
}
if (importAsConfigurationPropertiesBeansType != null) {
elements.addAll(roundEnv.getElementsAnnotatedWith(importAsConfigurationPropertiesBeansType));
} }
elements.forEach(this::processImportAsConfigurationPropertiesBean); return false;
} }
private Map<Element, List<Element>> getElementsAnnotatedOrMetaAnnotatedWith(RoundEnvironment roundEnv, private Map<Element, List<Element>> getElementsAnnotatedOrMetaAnnotatedWith(RoundEnvironment roundEnv,
...@@ -226,7 +184,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -226,7 +184,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
if (annotation != null) { if (annotation != null) {
String prefix = getPrefix(annotation); String prefix = getPrefix(annotation);
if (element instanceof TypeElement) { if (element instanceof TypeElement) {
processAnnotatedTypeElement(prefix, (TypeElement) element, false, new Stack<>()); processAnnotatedTypeElement(prefix, (TypeElement) element, new Stack<>());
} }
else if (element instanceof ExecutableElement) { else if (element instanceof ExecutableElement) {
processExecutableElement(prefix, (ExecutableElement) element, new Stack<>()); processExecutableElement(prefix, (ExecutableElement) element, new Stack<>());
...@@ -238,11 +196,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -238,11 +196,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
} }
} }
private void processAnnotatedTypeElement(String prefix, TypeElement element, boolean fromImport, private void processAnnotatedTypeElement(String prefix, TypeElement element, Stack<TypeElement> seen) {
Stack<TypeElement> seen) {
String type = this.metadataEnv.getTypeUtils().getQualifiedName(element); String type = this.metadataEnv.getTypeUtils().getQualifiedName(element);
this.metadataCollector.add(ItemMetadata.newGroup(prefix, type, type, null)); this.metadataCollector.add(ItemMetadata.newGroup(prefix, type, type, null));
processTypeElement(prefix, element, fromImport, null, seen); processTypeElement(prefix, element, null, seen);
} }
private void processExecutableElement(String prefix, ExecutableElement element, Stack<TypeElement> seen) { private void processExecutableElement(String prefix, ExecutableElement element, Stack<TypeElement> seen) {
...@@ -260,26 +217,25 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -260,26 +217,25 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
} }
else { else {
this.metadataCollector.add(group); this.metadataCollector.add(group);
processTypeElement(prefix, (TypeElement) returns, false, element, seen); processTypeElement(prefix, (TypeElement) returns, element, seen);
} }
} }
} }
} }
private void processTypeElement(String prefix, TypeElement element, boolean fromImport, ExecutableElement source, private void processTypeElement(String prefix, TypeElement element, ExecutableElement source,
Stack<TypeElement> seen) { Stack<TypeElement> seen) {
if (!seen.contains(element)) { if (!seen.contains(element)) {
seen.push(element); seen.push(element);
new PropertyDescriptorResolver(this.metadataEnv).resolve(element, fromImport, source) new PropertyDescriptorResolver(this.metadataEnv).resolve(element, source).forEach((descriptor) -> {
.forEach((descriptor) -> { this.metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv));
this.metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv)); if (descriptor.isNested(this.metadataEnv)) {
if (descriptor.isNested(this.metadataEnv)) { TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils()
TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils() .asElement(descriptor.getType());
.asElement(descriptor.getType()); String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, descriptor.getName());
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, descriptor.getName()); processTypeElement(nestedPrefix, nestedTypeElement, source, seen);
processTypeElement(nestedPrefix, nestedTypeElement, false, source, seen); }
} });
});
seen.pop(); seen.pop();
} }
} }
...@@ -316,31 +272,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor ...@@ -316,31 +272,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
} }
} }
private void processImportAsConfigurationPropertiesBean(Element element) {
this.metadataEnv.getImportAsConfigurationPropertiesBeanAnnotations(element)
.forEach(this::processImportAsConfigurationPropertiesBean);
}
@SuppressWarnings("unchecked")
private void processImportAsConfigurationPropertiesBean(AnnotationMirror annotation) {
String prefix = getPrefix(annotation);
processImportAsConfigurationPropertiesBeanTypes(prefix,
(List<TypeMirror>) this.metadataEnv.getAnnotationElementValues(annotation).get("type"));
processImportAsConfigurationPropertiesBeanTypes(prefix,
(List<TypeMirror>) this.metadataEnv.getAnnotationElementValues(annotation).get("value"));
}
private void processImportAsConfigurationPropertiesBeanTypes(String prefix, List<TypeMirror> types) {
if (types != null) {
for (TypeMirror type : types) {
Element element = this.metadataEnv.getTypeUtils().asElement(type);
AnnotationMirror annotation = this.metadataEnv.getConfigurationPropertiesAnnotation(element);
prefix = (annotation != null) ? getPrefix(annotation) : prefix;
processAnnotatedTypeElement(prefix, (TypeElement) element, true, new Stack<>());
}
}
}
private boolean hasMainReadOperation(TypeElement element) { private boolean hasMainReadOperation(TypeElement element) {
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) { for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
if (this.metadataEnv.getReadOperationAnnotation(method) != null if (this.metadataEnv.getReadOperationAnnotation(method) != null
......
...@@ -97,15 +97,10 @@ class MetadataGenerationEnvironment { ...@@ -97,15 +97,10 @@ class MetadataGenerationEnvironment {
private final String nameAnnotation; private final String nameAnnotation;
private final String importAsConfigurationPropertiesBeanAnnotation;
private final String importAsConfigurationPropertiesBeansAnnotation;
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation, MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation, String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
String constructorBindingAnnotation, String defaultValueAnnotation, String endpointAnnotation, String constructorBindingAnnotation, String defaultValueAnnotation, String endpointAnnotation,
String readOperationAnnotation, String nameAnnotation, String importAsConfigurationPropertiesBeanAnnotation, String readOperationAnnotation, String nameAnnotation) {
String importAsConfigurationPropertiesBeansAnnotation) {
this.typeUtils = new TypeUtils(environment); this.typeUtils = new TypeUtils(environment);
this.elements = environment.getElementUtils(); this.elements = environment.getElementUtils();
this.messager = environment.getMessager(); this.messager = environment.getMessager();
...@@ -118,8 +113,6 @@ class MetadataGenerationEnvironment { ...@@ -118,8 +113,6 @@ class MetadataGenerationEnvironment {
this.endpointAnnotation = endpointAnnotation; this.endpointAnnotation = endpointAnnotation;
this.readOperationAnnotation = readOperationAnnotation; this.readOperationAnnotation = readOperationAnnotation;
this.nameAnnotation = nameAnnotation; this.nameAnnotation = nameAnnotation;
this.importAsConfigurationPropertiesBeanAnnotation = importAsConfigurationPropertiesBeanAnnotation;
this.importAsConfigurationPropertiesBeansAnnotation = importAsConfigurationPropertiesBeansAnnotation;
} }
private static FieldValuesParser resolveFieldValuesParser(ProcessingEnvironment env) { private static FieldValuesParser resolveFieldValuesParser(ProcessingEnvironment env) {
...@@ -265,14 +258,6 @@ class MetadataGenerationEnvironment { ...@@ -265,14 +258,6 @@ class MetadataGenerationEnvironment {
return this.elements.getTypeElement(this.configurationPropertiesAnnotation); return this.elements.getTypeElement(this.configurationPropertiesAnnotation);
} }
TypeElement getImportAsConfigurationPropertiesBeansAnnotation() {
return this.elements.getTypeElement(this.importAsConfigurationPropertiesBeanAnnotation);
}
TypeElement getImportAsConfigurationPropertiesBeansAnnotationElement() {
return this.elements.getTypeElement(this.importAsConfigurationPropertiesBeansAnnotation);
}
AnnotationMirror getConfigurationPropertiesAnnotation(Element element) { AnnotationMirror getConfigurationPropertiesAnnotation(Element element) {
return getAnnotation(element, this.configurationPropertiesAnnotation); return getAnnotation(element, this.configurationPropertiesAnnotation);
} }
...@@ -297,22 +282,6 @@ class MetadataGenerationEnvironment { ...@@ -297,22 +282,6 @@ class MetadataGenerationEnvironment {
return getAnnotation(element, this.nameAnnotation); return getAnnotation(element, this.nameAnnotation);
} }
List<AnnotationMirror> getImportAsConfigurationPropertiesBeanAnnotations(Element element) {
List<AnnotationMirror> annotations = new ArrayList<>();
AnnotationMirror importBean = getAnnotation(element, this.importAsConfigurationPropertiesBeanAnnotation);
if (importBean != null) {
annotations.add(importBean);
}
AnnotationMirror importBeans = getAnnotation(element, this.importAsConfigurationPropertiesBeansAnnotation);
if (importBeans != null) {
AnnotationValue value = importBeans.getElementValues().values().iterator().next();
for (Object contained : (List<?>) value.getValue()) {
annotations.add((AnnotationMirror) contained);
}
}
return Collections.unmodifiableList(annotations);
}
boolean hasNullableAnnotation(Element element) { boolean hasNullableAnnotation(Element element) {
return getAnnotation(element, NULLABLE_ANNOTATION) != null; return getAnnotation(element, NULLABLE_ANNOTATION) != null;
} }
......
...@@ -49,19 +49,16 @@ class PropertyDescriptorResolver { ...@@ -49,19 +49,16 @@ class PropertyDescriptorResolver {
* specified {@link TypeElement type} based on the specified {@link ExecutableElement * specified {@link TypeElement type} based on the specified {@link ExecutableElement
* factory method}, if any. * factory method}, if any.
* @param type the target type * @param type the target type
* @param fromImport it the type was imported via a
* {@code @ImportAsConfigurationPropertiesBean}
* @param factoryMethod the method that triggered the metadata for that {@code type} * @param factoryMethod the method that triggered the metadata for that {@code type}
* or {@code null} * or {@code null}
* @return the candidate properties for metadata generation * @return the candidate properties for metadata generation
*/ */
Stream<PropertyDescriptor<?>> resolve(TypeElement type, boolean fromImport, ExecutableElement factoryMethod) { Stream<PropertyDescriptor<?>> resolve(TypeElement type, ExecutableElement factoryMethod) {
TypeElementMembers members = new TypeElementMembers(this.environment, type); TypeElementMembers members = new TypeElementMembers(this.environment, type);
if (factoryMethod != null) { if (factoryMethod != null) {
return resolveJavaBeanProperties(type, factoryMethod, members); return resolveJavaBeanProperties(type, factoryMethod, members);
} }
return resolve(ConfigurationPropertiesTypeElement.of(type, fromImport, this.environment), factoryMethod, return resolve(ConfigurationPropertiesTypeElement.of(type, this.environment), factoryMethod, members);
members);
} }
private Stream<PropertyDescriptor<?>> resolve(ConfigurationPropertiesTypeElement type, private Stream<PropertyDescriptor<?>> resolve(ConfigurationPropertiesTypeElement type,
...@@ -181,29 +178,20 @@ class PropertyDescriptorResolver { ...@@ -181,29 +178,20 @@ class PropertyDescriptorResolver {
return boundConstructor; return boundConstructor;
} }
static ConfigurationPropertiesTypeElement of(TypeElement type, boolean fromImport, static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) {
MetadataGenerationEnvironment env) { boolean constructorBoundType = isConstructorBoundType(type, env);
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
List<ExecutableElement> boundConstructors = constructors.stream() List<ExecutableElement> boundConstructors = constructors.stream()
.filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList()); .filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList());
boolean constructorBoundType = isConstructorBoundType(type, fromImport, constructors, env);
return new ConfigurationPropertiesTypeElement(type, constructorBoundType, constructors, boundConstructors); return new ConfigurationPropertiesTypeElement(type, constructorBoundType, constructors, boundConstructors);
} }
private static boolean isConstructorBoundType(TypeElement type, boolean fromImport, private static boolean isConstructorBoundType(TypeElement type, MetadataGenerationEnvironment env) {
List<ExecutableElement> constructors, MetadataGenerationEnvironment env) {
if (env.hasConstructorBindingAnnotation(type)) { if (env.hasConstructorBindingAnnotation(type)) {
return true; return true;
} }
if (type.getNestingKind() == NestingKind.MEMBER) { if (type.getNestingKind() == NestingKind.MEMBER) {
return isConstructorBoundType((TypeElement) type.getEnclosingElement(), false, constructors, env); return isConstructorBoundType((TypeElement) type.getEnclosingElement(), env);
}
if (fromImport) {
for (ExecutableElement constructor : constructors) {
if (!constructor.getParameters().isEmpty()) {
return true;
}
}
} }
return false; return false;
} }
......
/*
* Copyright 2012-2020 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
*
* https://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;
import org.junit.jupiter.api.Test;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.Metadata;
import org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBeans;
import org.springframework.boot.configurationsample.importbean.ImportAnnotatedJavaBean;
import org.springframework.boot.configurationsample.importbean.ImportJavaBeanConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportMultipleTypeConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportRepeatedConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportValueObjectConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportedAnnotatedJavaBean;
import org.springframework.boot.configurationsample.importbean.ImportedJavaBean;
import org.springframework.boot.configurationsample.importbean.ImportedValueObject;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ImportAsConfigurationPropertiesBean} and
* {@link ImportAsConfigurationPropertiesBeans}.
*
* @author Phillip Webb
*/
public class ImportBeanTests extends AbstractMetadataGenerationTests {
@Test
void importValueObjectConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportValueObjectConfigurationPropertiesBean.class);
assertThat(metadata)
.has(Metadata.withProperty("importbean.value", String.class).fromSource(ImportedValueObject.class));
}
@Test
void importJavaBeanConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportJavaBeanConfigurationPropertiesBean.class);
assertThat(metadata)
.has(Metadata.withProperty("importbean.name", String.class).fromSource(ImportedJavaBean.class));
}
@Test
void importMultipleTypeConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportMultipleTypeConfigurationPropertiesBean.class);
assertThat(metadata)
.has(Metadata.withProperty("importbean.value", String.class).fromSource(ImportedValueObject.class));
assertThat(metadata)
.has(Metadata.withProperty("importbean.name", String.class).fromSource(ImportedJavaBean.class));
}
@Test
void importRepeatedConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportRepeatedConfigurationPropertiesBean.class);
assertThat(metadata).has(Metadata.withProperty("vo.value", String.class).fromSource(ImportedValueObject.class));
assertThat(metadata).has(Metadata.withProperty("jb.name", String.class).fromSource(ImportedJavaBean.class));
}
@Test
void importAnnotatedJavaBean() {
ConfigurationMetadata metadata = compile(ImportAnnotatedJavaBean.class);
assertThat(metadata)
.has(Metadata.withProperty("test.name", String.class).fromSource(ImportedAnnotatedJavaBean.class));
}
}
...@@ -39,9 +39,7 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron ...@@ -39,9 +39,7 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION, TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION, TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION, TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.NAME_ANNOTATION, TestConfigurationMetadataAnnotationProcessor.NAME_ANNOTATION);
TestConfigurationMetadataAnnotationProcessor.IMPORT_AS_CONFIGURATION_PROPERTIES_BEAN_ANNOATION,
TestConfigurationMetadataAnnotationProcessor.IMPORT_AS_CONFIGURATION_PROPERTIES_BEANS_ANNOATION);
} }
} }
...@@ -74,9 +74,9 @@ class PropertyDescriptorResolverTests { ...@@ -74,9 +74,9 @@ class PropertyDescriptorResolverTests {
Arrays.asList(HierarchicalPropertiesParent.class, HierarchicalPropertiesGrandparent.class), Arrays.asList(HierarchicalPropertiesParent.class, HierarchicalPropertiesGrandparent.class),
(type, metadataEnv) -> { (type, metadataEnv) -> {
PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv); PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv);
assertThat(resolver.resolve(type, false, null).map(PropertyDescriptor::getName)) assertThat(resolver.resolve(type, null).map(PropertyDescriptor::getName)).containsExactly("third",
.containsExactly("third", "second", "first"); "second", "first");
assertThat(resolver.resolve(type, false, null) assertThat(resolver.resolve(type, null)
.map((descriptor) -> descriptor.resolveItemMetadata("test", metadataEnv)) .map((descriptor) -> descriptor.resolveItemMetadata("test", metadataEnv))
.map(ItemMetadata::getDefaultValue)).containsExactly("three", "two", "one"); .map(ItemMetadata::getDefaultValue)).containsExactly("three", "two", "one");
}); });
...@@ -155,7 +155,7 @@ class PropertyDescriptorResolverTests { ...@@ -155,7 +155,7 @@ class PropertyDescriptorResolverTests {
Consumer<Stream<PropertyDescriptor<?>>> stream) { Consumer<Stream<PropertyDescriptor<?>>> stream) {
return (element, metadataEnv) -> { return (element, metadataEnv) -> {
PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv); PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv);
stream.accept(resolver.resolve(element, false, null)); stream.accept(resolver.resolve(element, null));
}; };
} }
......
...@@ -57,10 +57,6 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM ...@@ -57,10 +57,6 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name"; public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name";
public static final String IMPORT_AS_CONFIGURATION_PROPERTIES_BEAN_ANNOATION = "org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBean";
public static final String IMPORT_AS_CONFIGURATION_PROPERTIES_BEANS_ANNOATION = "org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBeans";
private ConfigurationMetadata metadata; private ConfigurationMetadata metadata;
private final File outputLocation; private final File outputLocation;
...@@ -109,16 +105,6 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM ...@@ -109,16 +105,6 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
return NAME_ANNOTATION; return NAME_ANNOTATION;
} }
@Override
protected String importAsConfigurationPropertiesBeanAnnotation() {
return IMPORT_AS_CONFIGURATION_PROPERTIES_BEAN_ANNOATION;
}
@Override
protected String importAsConfigurationPropertiesBeansAnnotation() {
return IMPORT_AS_CONFIGURATION_PROPERTIES_BEANS_ANNOATION;
}
@Override @Override
protected ConfigurationMetadata writeMetaData() throws Exception { protected ConfigurationMetadata writeMetaData() throws Exception {
super.writeMetaData(); super.writeMetaData();
......
/*
* Copyright 2012-2020 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
*
* https://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.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Alternative to Spring Boot's {@code ImportAsConfigurationPropertiesBean} for testing
* (removes the need for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConfigurationProperties
@Repeatable(ImportAsConfigurationPropertiesBeans.class)
public @interface ImportAsConfigurationPropertiesBean {
Class<?>[] type();
@AliasFor(annotation = ConfigurationProperties.class)
String prefix() default "";
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreInvalidFields() default false;
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreUnknownFields() default true;
}
/*
* Copyright 2012-2020 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
*
* https://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 ImportAsConfigurationPropertiesBeans} for testing
* (removes the need for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ImportAsConfigurationPropertiesBeans {
ImportAsConfigurationPropertiesBean[] value();
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
import org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBean;
/**
* An import of a java bean.
*
* @author Phillip Webb
*/
@ImportAsConfigurationPropertiesBean(type = ImportedAnnotatedJavaBean.class)
public class ImportAnnotatedJavaBean {
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
import org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBean;
/**
* An import of a java bean.
*
* @author Phillip Webb
*/
@ImportAsConfigurationPropertiesBean(type = ImportedJavaBean.class, prefix = "importbean")
public class ImportJavaBeanConfigurationPropertiesBean {
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
import org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBean;
/**
* An import of a java bean and a value object.
*
* @author Phillip Webb
*/
@ImportAsConfigurationPropertiesBean(type = { ImportedJavaBean.class, ImportedValueObject.class },
prefix = "importbean")
public class ImportMultipleTypeConfigurationPropertiesBean {
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
import org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBean;
/**
* An import of a java bean and a value object.
*
* @author Phillip Webb
*/
@ImportAsConfigurationPropertiesBean(type = ImportedJavaBean.class, prefix = "jb")
@ImportAsConfigurationPropertiesBean(type = ImportedValueObject.class, prefix = "vo")
public class ImportRepeatedConfigurationPropertiesBean {
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
import org.springframework.boot.configurationsample.ImportAsConfigurationPropertiesBean;
/**
* An import of a value object.
*
* @author Phillip Webb
*/
@ImportAsConfigurationPropertiesBean(type = ImportedValueObject.class, prefix = "importbean")
public class ImportValueObjectConfigurationPropertiesBean {
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
import org.springframework.boot.configurationsample.ConfigurationProperties;
/**
* Java bean that can be imported.
*
* @author Phillip Webb
*/
@ConfigurationProperties(prefix = "test")
public class ImportedAnnotatedJavaBean {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
/**
* Java bean that can be imported.
*
* @author Phillip Webb
*/
public class ImportedJavaBean {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
/*
* Copyright 2012-2020 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
*
* https://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.importbean;
/**
* Value object that can be imported.
*
* @author Phillip Webb
*/
public class ImportedValueObject {
private final String value;
public ImportedValueObject(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
...@@ -18,7 +18,6 @@ package org.springframework.boot.context.properties; ...@@ -18,7 +18,6 @@ package org.springframework.boot.context.properties;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
...@@ -76,7 +75,7 @@ public final class ConfigurationPropertiesBean { ...@@ -76,7 +75,7 @@ public final class ConfigurationPropertiesBean {
this.instance = instance; this.instance = instance;
this.annotation = annotation; this.annotation = annotation;
this.bindTarget = bindTarget; this.bindTarget = bindTarget;
this.bindMethod = BindMethod.forBindable(bindTarget); this.bindMethod = BindMethod.forType(bindTarget.getType().resolve());
} }
/** /**
...@@ -176,8 +175,7 @@ public final class ConfigurationPropertiesBean { ...@@ -176,8 +175,7 @@ public final class ConfigurationPropertiesBean {
if (beanFactory.findAnnotationOnBean(beanName, ConfigurationProperties.class) != null) { if (beanFactory.findAnnotationOnBean(beanName, ConfigurationProperties.class) != null) {
return true; return true;
} }
BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName); Method factoryMethod = findFactoryMethod(beanFactory, beanName);
Method factoryMethod = findFactoryMethod(beanFactory, beanDefinition);
return findMergedAnnotation(factoryMethod, ConfigurationProperties.class).isPresent(); return findMergedAnnotation(factoryMethod, ConfigurationProperties.class).isPresent();
} }
catch (NoSuchBeanDefinitionException ex) { catch (NoSuchBeanDefinitionException ex) {
...@@ -199,41 +197,33 @@ public final class ConfigurationPropertiesBean { ...@@ -199,41 +197,33 @@ public final class ConfigurationPropertiesBean {
* {@link ConfigurationProperties @ConfigurationProperties} * {@link ConfigurationProperties @ConfigurationProperties}
*/ */
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) { public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
ConfigurableListableBeanFactory beanFactory = getBeanFactory(applicationContext); Method factoryMethod = findFactoryMethod(applicationContext, beanName);
BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName); return create(beanName, bean, bean.getClass(), factoryMethod);
Method factoryMethod = findFactoryMethod(beanFactory, beanDefinition);
ConfigurationProperties annotation = findAnnotation(beanDefinition);
boolean deduceBindConstructor = (beanDefinition instanceof ConfigurationPropertiesValueObjectBeanDefinition)
? ((ConfigurationPropertiesValueObjectBeanDefinition) beanDefinition).isDeduceBindConstructor() : false;
return create(beanName, bean, bean.getClass(), factoryMethod, annotation, deduceBindConstructor);
} }
private static ConfigurableListableBeanFactory getBeanFactory(ApplicationContext applicationContext) { private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) {
if (applicationContext instanceof ConfigurableApplicationContext) { if (applicationContext instanceof ConfigurableApplicationContext) {
return ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); return findFactoryMethod((ConfigurableApplicationContext) applicationContext, beanName);
} }
return null; return null;
} }
private static BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) { private static Method findFactoryMethod(ConfigurableApplicationContext applicationContext, String beanName) {
if (beanFactory != null && beanFactory.containsBeanDefinition(beanName)) { return findFactoryMethod(applicationContext.getBeanFactory(), beanName);
return beanFactory.getMergedBeanDefinition(beanName);
}
return null;
} }
private static Method findFactoryMethod(ConfigurableListableBeanFactory beanFactory, private static Method findFactoryMethod(ConfigurableListableBeanFactory beanFactory, String beanName) {
BeanDefinition beanDefinition) { if (beanFactory.containsBeanDefinition(beanName)) {
if (beanFactory == null || beanDefinition == null) { BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
return null; if (beanDefinition instanceof RootBeanDefinition) {
} Method resolvedFactoryMethod = ((RootBeanDefinition) beanDefinition).getResolvedFactoryMethod();
if (beanDefinition instanceof RootBeanDefinition) { if (resolvedFactoryMethod != null) {
Method resolvedFactoryMethod = ((RootBeanDefinition) beanDefinition).getResolvedFactoryMethod(); return resolvedFactoryMethod;
if (resolvedFactoryMethod != null) { }
return resolvedFactoryMethod;
} }
return findFactoryMethodUsingReflection(beanFactory, beanDefinition);
} }
return findFactoryMethodUsingReflection(beanFactory, beanDefinition); return null;
} }
private static Method findFactoryMethodUsingReflection(ConfigurableListableBeanFactory beanFactory, private static Method findFactoryMethodUsingReflection(ConfigurableListableBeanFactory beanFactory,
...@@ -256,19 +246,15 @@ public final class ConfigurationPropertiesBean { ...@@ -256,19 +246,15 @@ public final class ConfigurationPropertiesBean {
return factoryMethod.get(); return factoryMethod.get();
} }
static ConfigurationPropertiesBean forValueObject(Class<?> beanClass, String beanName, static ConfigurationPropertiesBean forValueObject(Class<?> beanClass, String beanName) {
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) { ConfigurationPropertiesBean propertiesBean = create(beanName, null, beanClass, null);
ConfigurationPropertiesBean propertiesBean = create(beanName, null, beanClass, null,
annotation.isPresent() ? annotation.synthesize() : null, deduceBindConstructor);
Assert.state(propertiesBean != null && propertiesBean.getBindMethod() == BindMethod.VALUE_OBJECT, Assert.state(propertiesBean != null && propertiesBean.getBindMethod() == BindMethod.VALUE_OBJECT,
() -> "Bean '" + beanName + "' is not a @ConfigurationProperties value object"); () -> "Bean '" + beanName + "' is not a @ConfigurationProperties value object");
return propertiesBean; return propertiesBean;
} }
private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory, private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
ConfigurationProperties annotation, boolean deduceBindConstructor) { ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
annotation = (annotation != null) ? annotation
: findAnnotation(instance, type, factory, ConfigurationProperties.class);
if (annotation == null) { if (annotation == null) {
return null; return null;
} }
...@@ -281,19 +267,9 @@ public final class ConfigurationPropertiesBean { ...@@ -281,19 +267,9 @@ public final class ConfigurationPropertiesBean {
if (instance != null) { if (instance != null) {
bindTarget = bindTarget.withExistingValue(instance); bindTarget = bindTarget.withExistingValue(instance);
} }
if (deduceBindConstructor) {
bindTarget = bindTarget.withAttribute(
ConfigurationPropertiesBindConstructorProvider.DEDUCE_BIND_CONSTRUCTOR_ATTRIUBTE, true);
}
return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget); return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
} }
private static ConfigurationProperties findAnnotation(BeanDefinition beanDefinition) {
MergedAnnotation<ConfigurationProperties> annotation = ConfigurationPropertiesBeanDefinition
.getAnnotation(beanDefinition);
return (annotation.isPresent()) ? annotation.synthesize() : null;
}
private static <A extends Annotation> A findAnnotation(Object instance, Class<?> type, Method factory, private static <A extends Annotation> A findAnnotation(Object instance, Class<?> type, Method factory,
Class<A> annotationType) { Class<A> annotationType) {
MergedAnnotation<A> annotation = MergedAnnotation.missing(); MergedAnnotation<A> annotation = MergedAnnotation.missing();
...@@ -332,27 +308,8 @@ public final class ConfigurationPropertiesBean { ...@@ -332,27 +308,8 @@ public final class ConfigurationPropertiesBean {
VALUE_OBJECT; VALUE_OBJECT;
static BindMethod forType(Class<?> type) { static BindMethod forType(Class<?> type) {
return forType(type, false); return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type, false) != null)
} ? VALUE_OBJECT : JAVA_BEAN;
static BindMethod forType(Class<?> type, boolean deduceBindConstructor) {
Constructor<?> constructor = ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(type, deduceBindConstructor, false);
if (deduceBindConstructor) {
Assert.state(constructor != null,
() -> "Unable to deduce @ConfigurationProperties bind method for " + type.getName());
}
return hasParameters(constructor) ? VALUE_OBJECT : JAVA_BEAN;
}
static BindMethod forBindable(Bindable<?> bindable) {
Constructor<?> constructor = ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(bindable, false);
return hasParameters(constructor) ? VALUE_OBJECT : JAVA_BEAN;
}
private static boolean hasParameters(Constructor<?> constructor) {
return constructor != null && constructor.getParameterCount() > 0;
} }
} }
......
/*
* Copyright 2012-2020 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
*
* https://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 org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.Conventions;
import org.springframework.core.annotation.MergedAnnotation;
/**
* {@link BeanDefinition} that is used for registering
* {@link ConfigurationProperties @ConfigurationProperties} beans.
*
* @author Phillip Webb
*/
class ConfigurationPropertiesBeanDefinition extends GenericBeanDefinition {
private static final String ANNOTATION_ATTRIBUTE = Conventions
.getQualifiedAttributeName(ConfigurationPropertiesBeanDefinition.class, "annotation");
ConfigurationPropertiesBeanDefinition(Class<?> beanClass, MergedAnnotation<ConfigurationProperties> annotation) {
setBeanClass(beanClass);
setAttribute(ANNOTATION_ATTRIBUTE, annotation);
}
@SuppressWarnings("unchecked")
static MergedAnnotation<ConfigurationProperties> getAnnotation(BeanDefinition beanDefinition) {
MergedAnnotation<ConfigurationProperties> annotation = (beanDefinition != null)
? (MergedAnnotation<ConfigurationProperties>) beanDefinition.getAttribute(ANNOTATION_ATTRIBUTE) : null;
return (annotation != null) ? annotation : MergedAnnotation.missing();
}
}
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -20,6 +20,7 @@ import org.springframework.beans.factory.HierarchicalBeanFactory; ...@@ -20,6 +20,7 @@ import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations;
...@@ -46,14 +47,16 @@ final class ConfigurationPropertiesBeanRegistrar { ...@@ -46,14 +47,16 @@ final class ConfigurationPropertiesBeanRegistrar {
this.beanFactory = (BeanFactory) this.registry; this.beanFactory = (BeanFactory) this.registry;
} }
void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) { void register(Class<?> type) {
MergedAnnotation<ConfigurationProperties> typeAnnotation = MergedAnnotations MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class); .from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
annotation = (!typeAnnotation.isPresent()) ? annotation : typeAnnotation; register(type, annotation);
annotation = (annotation != null) ? annotation : MergedAnnotation.missing(); }
void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
String name = getName(type, annotation); String name = getName(type, annotation);
if (!containsBeanDefinition(name)) { if (!containsBeanDefinition(name)) {
registerBeanDefinition(name, type, annotation, deduceBindConstructor); registerBeanDefinition(name, type, annotation);
} }
} }
...@@ -78,20 +81,19 @@ final class ConfigurationPropertiesBeanRegistrar { ...@@ -78,20 +81,19 @@ final class ConfigurationPropertiesBeanRegistrar {
} }
private void registerBeanDefinition(String beanName, Class<?> type, private void registerBeanDefinition(String beanName, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) { MergedAnnotation<ConfigurationProperties> annotation) {
Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName() Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
+ " annotation found on '" + type.getName() + "'."); + " annotation found on '" + type.getName() + "'.");
this.registry.registerBeanDefinition(beanName, this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
createBeanDefinition(beanName, type, annotation, deduceBindConstructor));
} }
private BeanDefinition createBeanDefinition(String beanName, Class<?> type, private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) { if (BindMethod.forType(type) == BindMethod.VALUE_OBJECT) {
if (BindMethod.forType(type, deduceBindConstructor) == BindMethod.VALUE_OBJECT) { return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type, annotation,
deduceBindConstructor);
} }
return new ConfigurationPropertiesBeanDefinition(type, annotation); GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
} }
} }
...@@ -21,7 +21,6 @@ import java.lang.reflect.Constructor; ...@@ -21,7 +21,6 @@ import java.lang.reflect.Constructor;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.bind.BindConstructorProvider; import org.springframework.boot.context.properties.bind.BindConstructorProvider;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.core.Conventions;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.Assert; import org.springframework.util.Assert;
...@@ -37,35 +36,19 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP ...@@ -37,35 +36,19 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider(); static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
static final String DEDUCE_BIND_CONSTRUCTOR_ATTRIUBTE = Conventions
.getQualifiedAttributeName(ConfigurationPropertiesBindConstructorProvider.class, "deduceBindConstructor");
@Override @Override
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) { public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
Boolean deduceBindConstructor = (Boolean) bindable.getAttribute(DEDUCE_BIND_CONSTRUCTOR_ATTRIUBTE); return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding);
return getBindConstructor(bindable.getType().resolve(), Boolean.TRUE.equals(deduceBindConstructor),
isNestedConstructorBinding);
} }
Constructor<?> getBindConstructor(Class<?> type, boolean deduceBindConstructor, Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) {
boolean isNestedConstructorBinding) {
if (type == null) { if (type == null) {
return null; return null;
} }
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type); Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
if (constructor != null) { if (constructor == null && (isConstructorBindingAnnotatedType(type) || isNestedConstructorBinding)) {
return constructor;
}
boolean isConstructorBindingAnnotatedType = isConstructorBindingAnnotatedType(type);
if (deduceBindConstructor || isNestedConstructorBinding || isConstructorBindingAnnotatedType) {
constructor = deduceBindConstructor(type); constructor = deduceBindConstructor(type);
} }
if (deduceBindConstructor && isConstructorBindingAnnotatedType && !isNestedConstructorBinding) {
Assert.state(constructor != null,
() -> "Unable to deduce constructor for @ConstructorBinding class " + type.getName());
Assert.state(constructor.getParameterCount() > 0,
() -> "Deduced no-args constructor for @ConstructorBinding class " + type.getName());
}
return constructor; return constructor;
} }
...@@ -103,7 +86,7 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP ...@@ -103,7 +86,7 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
return deducedKotlinBindConstructor(type); return deducedKotlinBindConstructor(type);
} }
Constructor<?>[] constructors = type.getDeclaredConstructors(); Constructor<?>[] constructors = type.getDeclaredConstructors();
if (constructors.length == 1) { if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
return constructors[0]; return constructors[0];
} }
return null; return null;
...@@ -111,7 +94,7 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP ...@@ -111,7 +94,7 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
private Constructor<?> deducedKotlinBindConstructor(Class<?> type) { private Constructor<?> deducedKotlinBindConstructor(Class<?> type) {
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type); Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type);
if (primaryConstructor != null) { if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) {
return primaryConstructor; return primaryConstructor;
} }
return null; return null;
......
...@@ -25,7 +25,6 @@ import org.springframework.beans.BeansException; ...@@ -25,7 +25,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
...@@ -207,14 +206,7 @@ class ConfigurationPropertiesBinder { ...@@ -207,14 +206,7 @@ class ConfigurationPropertiesBinder {
} }
static ConfigurationPropertiesBinder get(BeanFactory beanFactory) { static ConfigurationPropertiesBinder get(BeanFactory beanFactory) {
try { return beanFactory.getBean(BEAN_NAME, ConfigurationPropertiesBinder.class);
return beanFactory.getBean(BEAN_NAME, ConfigurationPropertiesBinder.class);
}
catch (NoSuchBeanDefinitionException ex) {
throw new NoSuchBeanDefinitionException(ex.getBeanName(),
"Unable to find ConfigurationPropertiesBinder bean '" + BEAN_NAME
+ "', ensure @EnableConfigurationProperties has been specified");
}
} }
/** /**
......
...@@ -108,7 +108,7 @@ class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegist ...@@ -108,7 +108,7 @@ class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegist
private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) { private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) {
if (!isComponent(type)) { if (!isComponent(type)) {
registrar.register(type, null, false); registrar.register(type);
} }
} }
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,7 +18,7 @@ package org.springframework.boot.context.properties; ...@@ -18,7 +18,7 @@ package org.springframework.boot.context.properties;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.beans.factory.support.GenericBeanDefinition;
/** /**
* {@link BeanDefinition} that is used for registering * {@link BeanDefinition} that is used for registering
...@@ -29,30 +29,21 @@ import org.springframework.core.annotation.MergedAnnotation; ...@@ -29,30 +29,21 @@ import org.springframework.core.annotation.MergedAnnotation;
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
*/ */
final class ConfigurationPropertiesValueObjectBeanDefinition extends ConfigurationPropertiesBeanDefinition { final class ConfigurationPropertiesValueObjectBeanDefinition extends GenericBeanDefinition {
private final BeanFactory beanFactory; private final BeanFactory beanFactory;
private final String beanName; private final String beanName;
private final boolean deduceBindConstructor; ConfigurationPropertiesValueObjectBeanDefinition(BeanFactory beanFactory, String beanName, Class<?> beanClass) {
ConfigurationPropertiesValueObjectBeanDefinition(BeanFactory beanFactory, String beanName, Class<?> beanClass,
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) {
super(beanClass, annotation);
this.beanFactory = beanFactory; this.beanFactory = beanFactory;
this.beanName = beanName; this.beanName = beanName;
this.deduceBindConstructor = deduceBindConstructor; setBeanClass(beanClass);
setInstanceSupplier(this::createBean); setInstanceSupplier(this::createBean);
} }
boolean isDeduceBindConstructor() {
return this.deduceBindConstructor;
}
private Object createBean() { private Object createBean() {
ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.forValueObject(getBeanClass(), this.beanName, ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.forValueObject(getBeanClass(), this.beanName);
getAnnotation(this), this.deduceBindConstructor);
ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(this.beanFactory); ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(this.beanFactory);
try { try {
return binder.bindOrCreate(bean); return binder.bindOrCreate(bean);
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -51,7 +51,6 @@ public @interface EnableConfigurationProperties { ...@@ -51,7 +51,6 @@ public @interface EnableConfigurationProperties {
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans with * {@link ConfigurationProperties @ConfigurationProperties} annotated beans with
* Spring. Standard Spring Beans will also be scanned regardless of this value. * Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@code @ConfigurationProperties} annotated beans to register * @return {@code @ConfigurationProperties} annotated beans to register
* @see ImportAsConfigurationPropertiesBean
*/ */
Class<?>[] value() default {}; Class<?>[] value() default {};
......
...@@ -37,9 +37,7 @@ class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegi ...@@ -37,9 +37,7 @@ class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegi
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry); registerInfrastructureBeans(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry); ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
for (Class<?> type : getTypes(metadata)) { getTypes(metadata).forEach(beanRegistrar::register);
beanRegistrar.register(type, null, false);
}
} }
private Set<Class<?>> getTypes(AnnotationMetadata metadata) { private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
......
/*
* Copyright 2012-2020 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
*
* https://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.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
/**
* Imports classes as {@link ConfigurationProperties @ConfigurationProperties} beans. Can
* be used to import {@link ConfigurationProperties @ConfigurationProperties} annotated
* types or third-party classes as configuration property beans.
* <p>
* Classes imported via this annotation that have a default constructor will use
* {@code setter} binding, those with a non-default constructor will use
* {@link ConstructorBinding @ConstructorBinding}. If you are looking to inject beans into
* a constructor, you should use a regular {@link Configuration @Configuration} class
* {@code @Bean} method instead.
* <p>
* The {@code @ConfigurationProperties} alias attributes defined on this class will only
* be used if the imported class is not itself annotated
* with{@code @ConfigurationProperties}.
*
* @author Phillip Webb
* @since 2.4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableConfigurationProperties
@ConfigurationProperties
@Repeatable(ImportAsConfigurationPropertiesBeans.class)
@Import(ImportAsConfigurationPropertiesBeanRegistrar.class)
public @interface ImportAsConfigurationPropertiesBean {
/**
* One or more types that should be imported as a bean.
* @return the types to import
*/
@AliasFor("type")
Class<?>[] value() default {};
/**
* One or more types that should be imported as a bean.
* @return the types to import
*/
@AliasFor("value")
Class<?>[] type() default {};
/**
* The prefix of the properties that are valid to bind to this object. A valid prefix
* is defined by one or more words separated with dots (e.g.
* {@code "acme.system.feature"}).
* @return the prefix of the properties to bind
* @see ConfigurationProperties#prefix()
*/
@AliasFor(annotation = ConfigurationProperties.class)
String prefix() default "";
/**
* Flag to indicate that when binding to this object invalid fields should be ignored.
* Invalid means invalid according to the binder that is used, and usually this means
* fields of the wrong type (or that cannot be coerced into the correct type).
* @return the flag value (default false)
* @see ConfigurationProperties#ignoreInvalidFields()
*/
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreInvalidFields() default false;
/**
* Flag to indicate that when binding to this object unknown fields should be ignored.
* An unknown field could be a sign of a mistake in the Properties.
* @return the flag value (default true)
* @see ConfigurationProperties#ignoreUnknownFields()
*/
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreUnknownFields() default true;
}
/*
* Copyright 2012-2020 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
*
* https://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 org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link ImportBeanDefinitionRegistrar} for
* {@link ImportAsConfigurationPropertiesBean @ImportAsConfigurationPropertiesBean}.
*
* @author Phillip Webb
*/
class ImportAsConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
try {
ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(registry);
MergedAnnotations annotations = importingClassMetadata.getAnnotations();
registerBeans(registrar, annotations.get(ImportAsConfigurationPropertiesBeans.class));
registerBean(registrar, annotations.get(ImportAsConfigurationPropertiesBean.class));
}
catch (RuntimeException ex) {
throw new IllegalStateException("Unable process @ImportAsConfigurationPropertiesBean annotations from "
+ importingClassMetadata.getClassName(), ex);
}
}
private void registerBeans(ConfigurationPropertiesBeanRegistrar registrar,
MergedAnnotation<ImportAsConfigurationPropertiesBeans> annotation) {
if (!annotation.isPresent()) {
return;
}
for (MergedAnnotation<ImportAsConfigurationPropertiesBean> containedAnnotation : annotation
.getAnnotationArray(MergedAnnotation.VALUE, ImportAsConfigurationPropertiesBean.class)) {
registerBean(registrar, containedAnnotation);
}
}
private void registerBean(ConfigurationPropertiesBeanRegistrar registrar,
MergedAnnotation<ImportAsConfigurationPropertiesBean> annotation) {
if (!annotation.isPresent()) {
return;
}
Class<?>[] types = annotation.getClassArray("type");
Assert.state(!ObjectUtils.isEmpty(types), "@ImportAsConfigurationPropertiesBean must declare types to import");
MergedAnnotation<ConfigurationProperties> configurationPropertiesAnnotation = MergedAnnotations
.from(annotation.synthesize()).get(ConfigurationProperties.class);
for (Class<?> type : types) {
registrar.register(type, configurationPropertiesAnnotation, true);
}
}
}
/*
* Copyright 2012-2020 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
*
* https://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;
import org.springframework.context.annotation.Import;
/**
* Container annotation that aggregates several
* {@link ImportAsConfigurationPropertiesBean @ImportAsConfigurationPropertiesBean}
* annotations.
*
* @author Phillip Webb
* @since 2.4.0
* @see ImportAsConfigurationPropertiesBean
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableConfigurationProperties
@Import(ImportAsConfigurationPropertiesBeanRegistrar.class)
public @interface ImportAsConfigurationPropertiesBeans {
/**
* The contained
* {@link ImportAsConfigurationPropertiesBean @ImportAsConfigurationPropertiesBean}
* annotations.
* @return the contained annotations
*/
ImportAsConfigurationPropertiesBean[] value();
}
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,8 +18,6 @@ package org.springframework.boot.context.properties.bind; ...@@ -18,8 +18,6 @@ package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -44,8 +42,6 @@ public final class Bindable<T> { ...@@ -44,8 +42,6 @@ public final class Bindable<T> {
private static final Annotation[] NO_ANNOTATIONS = {}; private static final Annotation[] NO_ANNOTATIONS = {};
private static final Map<String, Object> NO_ATTRIBUTES = Collections.emptyMap();
private final ResolvableType type; private final ResolvableType type;
private final ResolvableType boxedType; private final ResolvableType boxedType;
...@@ -54,15 +50,11 @@ public final class Bindable<T> { ...@@ -54,15 +50,11 @@ public final class Bindable<T> {
private final Annotation[] annotations; private final Annotation[] annotations;
private final Map<String, Object> attributes; private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations) {
private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations,
Map<String, Object> attributes) {
this.type = type; this.type = type;
this.boxedType = boxedType; this.boxedType = boxedType;
this.value = value; this.value = value;
this.annotations = annotations; this.annotations = annotations;
this.attributes = attributes;
} }
/** /**
...@@ -113,16 +105,6 @@ public final class Bindable<T> { ...@@ -113,16 +105,6 @@ public final class Bindable<T> {
return null; return null;
} }
/**
* Return the value of an attribute that has been associated with this
* {@link Bindable}.
* @param name the attribute name
* @return the associated attribute value or {@code null}
*/
public Object getAttribute(String name) {
return this.attributes.get(name);
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) { if (this == obj) {
...@@ -135,7 +117,6 @@ public final class Bindable<T> { ...@@ -135,7 +117,6 @@ public final class Bindable<T> {
boolean result = true; boolean result = true;
result = result && nullSafeEquals(this.type.resolve(), other.type.resolve()); result = result && nullSafeEquals(this.type.resolve(), other.type.resolve());
result = result && nullSafeEquals(this.annotations, other.annotations); result = result && nullSafeEquals(this.annotations, other.annotations);
result = result && nullSafeEquals(this.attributes, other.attributes);
return result; return result;
} }
...@@ -145,7 +126,6 @@ public final class Bindable<T> { ...@@ -145,7 +126,6 @@ public final class Bindable<T> {
int result = 1; int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.type); result = prime * result + ObjectUtils.nullSafeHashCode(this.type);
result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations); result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations);
result = prime * result + ObjectUtils.nullSafeHashCode(this.attributes);
return result; return result;
} }
...@@ -155,7 +135,6 @@ public final class Bindable<T> { ...@@ -155,7 +135,6 @@ public final class Bindable<T> {
creator.append("type", this.type); creator.append("type", this.type);
creator.append("value", (this.value != null) ? "provided" : "none"); creator.append("value", (this.value != null) ? "provided" : "none");
creator.append("annotations", this.annotations); creator.append("annotations", this.annotations);
creator.append("attributes", this.attributes);
return creator.toString(); return creator.toString();
} }
...@@ -170,19 +149,7 @@ public final class Bindable<T> { ...@@ -170,19 +149,7 @@ public final class Bindable<T> {
*/ */
public Bindable<T> withAnnotations(Annotation... annotations) { public Bindable<T> withAnnotations(Annotation... annotations) {
return new Bindable<>(this.type, this.boxedType, this.value, return new Bindable<>(this.type, this.boxedType, this.value,
(annotations != null) ? annotations : NO_ANNOTATIONS, this.attributes); (annotations != null) ? annotations : NO_ANNOTATIONS);
}
/**
* Create an updated {@link Bindable} instance with the specified attribute.
* @param name the attribute name
* @param value the attribute value
* @return an updated {@link Bindable}
*/
public Bindable<T> withAttribute(String name, Object value) {
Map<String, Object> attributes = new HashMap<>(this.attributes);
attributes.put(name, value);
return new Bindable<>(this.type, this.boxedType, this.value, this.annotations, attributes);
} }
/** /**
...@@ -195,7 +162,7 @@ public final class Bindable<T> { ...@@ -195,7 +162,7 @@ public final class Bindable<T> {
existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue), existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue),
() -> "ExistingValue must be an instance of " + this.type); () -> "ExistingValue must be an instance of " + this.type);
Supplier<T> value = (existingValue != null) ? () -> existingValue : null; Supplier<T> value = (existingValue != null) ? () -> existingValue : null;
return new Bindable<>(this.type, this.boxedType, value, this.annotations, this.attributes); return new Bindable<>(this.type, this.boxedType, value, this.annotations);
} }
/** /**
...@@ -204,7 +171,7 @@ public final class Bindable<T> { ...@@ -204,7 +171,7 @@ public final class Bindable<T> {
* @return an updated {@link Bindable} * @return an updated {@link Bindable}
*/ */
public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue) { public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue) {
return new Bindable<>(this.type, this.boxedType, suppliedValue, this.annotations, this.attributes); return new Bindable<>(this.type, this.boxedType, suppliedValue, this.annotations);
} }
/** /**
...@@ -277,7 +244,7 @@ public final class Bindable<T> { ...@@ -277,7 +244,7 @@ public final class Bindable<T> {
public static <T> Bindable<T> of(ResolvableType type) { public static <T> Bindable<T> of(ResolvableType type) {
Assert.notNull(type, "Type must not be null"); Assert.notNull(type, "Type must not be null");
ResolvableType boxedType = box(type); ResolvableType boxedType = box(type);
return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS, NO_ATTRIBUTES); return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS);
} }
private static ResolvableType box(ResolvableType type) { private static ResolvableType box(ResolvableType type) {
......
/*
* Copyright 2012-2020 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
*
* https://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 org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.annotation.MergedAnnotation;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertiesBeanDefinition}.
*
* @author Phillip Webb
*/
class ConfigurationPropertiesBeanDefinitionTests {
@Test
void getAnnotationGetsAnnotation() {
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotation.of(ConfigurationProperties.class);
BeanDefinition definition = new ConfigurationPropertiesBeanDefinition(Example.class, annotation);
assertThat(ConfigurationPropertiesBeanDefinition.getAnnotation(definition)).isSameAs(annotation);
}
@Test
void getAnnotationWhenNullReturnsMissing() {
assertThat(ConfigurationPropertiesBeanDefinition.getAnnotation(null)).isEqualTo(MergedAnnotation.missing());
}
@Test
void getAnnotationWhenNoAttributeReturnsMissing() {
GenericBeanDefinition definition = new GenericBeanDefinition();
assertThat(ConfigurationPropertiesBeanDefinition.getAnnotation(definition))
.isEqualTo(MergedAnnotation.missing());
}
static class Example {
}
}
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -22,7 +22,6 @@ import org.springframework.beans.factory.config.BeanDefinition; ...@@ -22,7 +22,6 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.annotation.MergedAnnotation;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
...@@ -43,7 +42,7 @@ class ConfigurationPropertiesBeanRegistrarTests { ...@@ -43,7 +42,7 @@ class ConfigurationPropertiesBeanRegistrarTests {
@Test @Test
void registerWhenNotAlreadyRegisteredAddBeanDefinition() { void registerWhenNotAlreadyRegisteredAddBeanDefinition() {
String beanName = "beancp-" + BeanConfigurationProperties.class.getName(); String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
this.registrar.register(BeanConfigurationProperties.class, null, false); this.registrar.register(BeanConfigurationProperties.class);
BeanDefinition definition = this.registry.getBeanDefinition(beanName); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isNotNull(); assertThat(definition).isNotNull();
assertThat(definition.getBeanClassName()).isEqualTo(BeanConfigurationProperties.class.getName()); assertThat(definition.getBeanClassName()).isEqualTo(BeanConfigurationProperties.class.getName());
...@@ -53,7 +52,7 @@ class ConfigurationPropertiesBeanRegistrarTests { ...@@ -53,7 +52,7 @@ class ConfigurationPropertiesBeanRegistrarTests {
void registerWhenAlreadyContainsNameDoesNotReplace() { void registerWhenAlreadyContainsNameDoesNotReplace() {
String beanName = "beancp-" + BeanConfigurationProperties.class.getName(); String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
this.registry.registerBeanDefinition(beanName, new GenericBeanDefinition()); this.registry.registerBeanDefinition(beanName, new GenericBeanDefinition());
this.registrar.register(BeanConfigurationProperties.class, null, false); this.registrar.register(BeanConfigurationProperties.class);
BeanDefinition definition = this.registry.getBeanDefinition(beanName); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isNotNull(); assertThat(definition).isNotNull();
assertThat(definition.getBeanClassName()).isNull(); assertThat(definition.getBeanClassName()).isNull();
...@@ -62,42 +61,24 @@ class ConfigurationPropertiesBeanRegistrarTests { ...@@ -62,42 +61,24 @@ class ConfigurationPropertiesBeanRegistrarTests {
@Test @Test
void registerWhenNoAnnotationThrowsException() { void registerWhenNoAnnotationThrowsException() {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> this.registrar.register(NoAnnotationConfigurationProperties.class, null, false)) .isThrownBy(() -> this.registrar.register(NoAnnotationConfigurationProperties.class))
.withMessageContaining("No ConfigurationProperties annotation found"); .withMessageContaining("No ConfigurationProperties annotation found");
} }
@Test @Test
void registerWhenValueObjectRegistersValueObjectBeanDefinition() { void registerWhenValueObjectRegistersValueObjectBeanDefinition() {
String beanName = "valuecp-" + ValueObjectConfigurationProperties.class.getName(); String beanName = "valuecp-" + ValueObjectConfigurationProperties.class.getName();
this.registrar.register(ValueObjectConfigurationProperties.class, null, false); this.registrar.register(ValueObjectConfigurationProperties.class);
BeanDefinition definition = this.registry.getBeanDefinition(beanName); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class); assertThat(definition).isInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
} }
@Test @Test
void registerWhenNotValueObjectRegistersConfigurationPropertiesBeanDefinition() { void registerWhenNotValueObjectRegistersGenericBeanDefinition() {
String beanName = MultiConstructorBeanConfigurationProperties.class.getName(); String beanName = MultiConstructorBeanConfigurationProperties.class.getName();
this.registrar.register(MultiConstructorBeanConfigurationProperties.class, null, false); this.registrar.register(MultiConstructorBeanConfigurationProperties.class);
BeanDefinition definition = this.registry.getBeanDefinition(beanName); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(definition).isInstanceOf(GenericBeanDefinition.class);
}
@Test
void registerWhenDeduceBindConstructorRegistersValueObjectBeanDefinition() {
String beanName = DeducedValueObjectConfigurationProperties.class.getName();
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotation.of(ConfigurationProperties.class);
this.registrar.register(DeducedValueObjectConfigurationProperties.class, annotation, true);
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
}
@Test
void registerWhenDeduceBindConstructorRegistersJavaBeanObjectBeanDefinition() {
String beanName = DeducedJavaBeanConfigurationProperties.class.getName();
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotation.of(ConfigurationProperties.class);
this.registrar.register(DeducedJavaBeanConfigurationProperties.class, annotation, true);
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
} }
@ConfigurationProperties(prefix = "beancp") @ConfigurationProperties(prefix = "beancp")
...@@ -129,15 +110,4 @@ class ConfigurationPropertiesBeanRegistrarTests { ...@@ -129,15 +110,4 @@ class ConfigurationPropertiesBeanRegistrarTests {
} }
static class DeducedValueObjectConfigurationProperties {
DeducedValueObjectConfigurationProperties(String name) {
}
}
static class DeducedJavaBeanConfigurationProperties {
}
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -30,7 +30,6 @@ import org.springframework.context.annotation.Import; ...@@ -30,7 +30,6 @@ import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector; import org.springframework.context.annotation.ImportSelector;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
...@@ -203,8 +202,8 @@ class ConfigurationPropertiesBeanTests { ...@@ -203,8 +202,8 @@ class ConfigurationPropertiesBeanTests {
@Test @Test
void forValueObjectReturnsBean() { void forValueObjectReturnsBean() {
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.forValueObject( ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean
ConstructorBindingOnConstructor.class, "valueObjectBean", MergedAnnotation.missing(), false); .forValueObject(ConstructorBindingOnConstructor.class, "valueObjectBean");
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean"); assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull(); assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(ConstructorBindingOnConstructor.class); assertThat(propertiesBean.getType()).isEqualTo(ConstructorBindingOnConstructor.class);
...@@ -214,34 +213,17 @@ class ConfigurationPropertiesBeanTests { ...@@ -214,34 +213,17 @@ class ConfigurationPropertiesBeanTests {
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class)); assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
assertThat(target.getValue()).isNull(); assertThat(target.getValue()).isNull();
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(ConstructorBindingOnConstructor.class, false, false)).isNotNull(); .getBindConstructor(ConstructorBindingOnConstructor.class, false)).isNotNull();
}
@Test
void forValueObjectWhenDeduceConstructorReturnsBean() {
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean
.forValueObject(DeducedConstructorBinding.class, "valueObjectBean", MergedAnnotation.missing(), true);
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(DeducedConstructorBinding.class);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
assertThat(propertiesBean.getAnnotation()).isNotNull();
Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(DeducedConstructorBinding.class));
assertThat(target.getValue()).isNull();
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(ConstructorBindingOnConstructor.class, false, false)).isNotNull();
} }
@Test @Test
void forValueObjectWhenJavaBeanBindTypeThrowsException() { void forValueObjectWhenJavaBeanBindTypeThrowsException() {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> ConfigurationPropertiesBean.forValueObject(AnnotatedBean.class, "annotatedBean", .isThrownBy(() -> ConfigurationPropertiesBean.forValueObject(AnnotatedBean.class, "annotatedBean"))
MergedAnnotation.missing(), false))
.withMessage("Bean 'annotatedBean' is not a @ConfigurationProperties value object"); .withMessage("Bean 'annotatedBean' is not a @ConfigurationProperties value object");
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> ConfigurationPropertiesBean.forValueObject(NonAnnotatedBean.class, "nonAnnotatedBean", .isThrownBy(
MergedAnnotation.missing(), false)) () -> ConfigurationPropertiesBean.forValueObject(NonAnnotatedBean.class, "nonAnnotatedBean"))
.withMessage("Bean 'nonAnnotatedBean' is not a @ConfigurationProperties value object"); .withMessage("Bean 'nonAnnotatedBean' is not a @ConfigurationProperties value object");
} }
...@@ -259,7 +241,7 @@ class ConfigurationPropertiesBeanTests { ...@@ -259,7 +241,7 @@ class ConfigurationPropertiesBeanTests {
} }
@Test @Test
void bindTypeForTypeWhenConstructorBindingOnConstructorReturnsValueObject() { void bindTypeForTypeWhenNoConstructorBindingOnConstructorReturnsValueObject() {
BindMethod bindType = BindMethod.forType(ConstructorBindingOnConstructor.class); BindMethod bindType = BindMethod.forType(ConstructorBindingOnConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT); assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
} }
...@@ -272,18 +254,6 @@ class ConfigurationPropertiesBeanTests { ...@@ -272,18 +254,6 @@ class ConfigurationPropertiesBeanTests {
+ " has more than one @ConstructorBinding constructor"); + " has more than one @ConstructorBinding constructor");
} }
@Test
void bindTypeForTypeWhenDeducedConstructorBindingOnValueObjectReturnsValueObject() {
BindMethod bindType = BindMethod.forType(DeducedConstructorBinding.class, true);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
}
@Test
void bindTypeForTypeWhenDeducedConstructorBindingOnJavaBeanReturnsJavABean() {
BindMethod bindType = BindMethod.forType(NonAnnotatedBean.class, true);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
}
private void get(Class<?> configuration, String beanName, ThrowingConsumer<ConfigurationPropertiesBean> consumer) private void get(Class<?> configuration, String beanName, ThrowingConsumer<ConfigurationPropertiesBean> consumer)
throws Throwable { throws Throwable {
get(configuration, beanName, true, consumer); get(configuration, beanName, true, consumer);
...@@ -504,14 +474,6 @@ class ConfigurationPropertiesBeanTests { ...@@ -504,14 +474,6 @@ class ConfigurationPropertiesBeanTests {
} }
@ConfigurationProperties
static class DeducedConstructorBinding {
DeducedConstructorBinding(String name) {
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Import(NonAnnotatedBeanConfigurationImportSelector.class) @Import(NonAnnotatedBeanConfigurationImportSelector.class)
static class NonAnnotatedBeanImportConfiguration { static class NonAnnotatedBeanImportConfiguration {
......
...@@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; ...@@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.scan.combined.c.CombinedConfiguration; import org.springframework.boot.context.properties.scan.combined.c.CombinedConfiguration;
import org.springframework.boot.context.properties.scan.combined.d.OtherCombinedConfiguration; import org.springframework.boot.context.properties.scan.combined.d.OtherCombinedConfiguration;
import org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration; import org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration;
...@@ -52,8 +53,8 @@ class ConfigurationPropertiesScanRegistrarTests { ...@@ -52,8 +53,8 @@ class ConfigurationPropertiesScanRegistrarTests {
"foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties"); "foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties");
BeanDefinition barDefinition = this.beanFactory.getBeanDefinition( BeanDefinition barDefinition = this.beanFactory.getBeanDefinition(
"bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties"); "bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties");
assertThat(bingDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(bingDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(fooDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(barDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class); assertThat(barDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
} }
...@@ -65,7 +66,7 @@ class ConfigurationPropertiesScanRegistrarTests { ...@@ -65,7 +66,7 @@ class ConfigurationPropertiesScanRegistrarTests {
getAnnotationMetadata(ConfigurationPropertiesScanConfiguration.TestConfiguration.class), beanFactory); getAnnotationMetadata(ConfigurationPropertiesScanConfiguration.TestConfiguration.class), beanFactory);
BeanDefinition fooDefinition = beanFactory.getBeanDefinition( BeanDefinition fooDefinition = beanFactory.getBeanDefinition(
"foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties"); "foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties");
assertThat(fooDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
} }
@Test @Test
...@@ -84,11 +85,11 @@ class ConfigurationPropertiesScanRegistrarTests { ...@@ -84,11 +85,11 @@ class ConfigurationPropertiesScanRegistrarTests {
"b.first-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BFirstProperties"); "b.first-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BFirstProperties");
BeanDefinition bSecondDefinition = beanFactory.getBeanDefinition( BeanDefinition bSecondDefinition = beanFactory.getBeanDefinition(
"b.second-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BSecondProperties"); "b.second-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BSecondProperties");
assertThat(aDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(aDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
// Constructor injection // Constructor injection
assertThat(bFirstDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class); assertThat(bFirstDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
// Post-processing injection // Post-processing injection
assertThat(bSecondDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(bSecondDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
} }
@Test @Test
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; ...@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -51,11 +52,11 @@ class EnableConfigurationPropertiesRegistrarTests { ...@@ -51,11 +52,11 @@ class EnableConfigurationPropertiesRegistrarTests {
} }
@Test @Test
void typeWithDefaultConstructorShouldRegisterConfigurationPropertiesBeanDefinition() throws Exception { void typeWithDefaultConstructorShouldRegisterGenericBeanDefinition() throws Exception {
register(TestConfiguration.class); register(TestConfiguration.class);
BeanDefinition beanDefinition = this.beanFactory BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("foo-" + getClass().getName() + "$FooProperties"); .getBeanDefinition("foo-" + getClass().getName() + "$FooProperties");
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
} }
@Test @Test
...@@ -67,11 +68,11 @@ class EnableConfigurationPropertiesRegistrarTests { ...@@ -67,11 +68,11 @@ class EnableConfigurationPropertiesRegistrarTests {
} }
@Test @Test
void typeWithMultipleConstructorsShouldRegisterConfigurationPropertiesBeanDefinition() throws Exception { void typeWithMultipleConstructorsShouldRegisterGenericBeanDefinition() throws Exception {
register(TestConfiguration.class); register(TestConfiguration.class);
BeanDefinition beanDefinition = this.beanFactory BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("bing-" + getClass().getName() + "$BingProperties"); .getBeanDefinition("bing-" + getClass().getName() + "$BingProperties");
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
} }
@Test @Test
......
/*
* Copyright 2012-2020 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
*
* https://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 org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.support.TestPropertySourceUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ImportAsConfigurationPropertiesBean}.
*
* @author Phillip Webb
*/
class ImportAsConfigurationPropertiesBeanTests {
@Test
void importJavaBean() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.name=spring");
context.register(JavaBeanConfig.class);
context.refresh();
assertThat(context.getBean(JavaBean.class).getName()).isEqualTo("spring");
}
}
@Test
void importValueObject() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.value=spring");
context.register(ValueObjectConfig.class);
context.refresh();
assertThat(context.getBean(ValueObject.class).getValue()).isEqualTo("spring");
}
}
@Test
void importMultiConstructorValueObjectFails() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.name=spring");
context.register(MultiConstructorValueObjectConfig.class);
assertThatIllegalStateException().isThrownBy(context::refresh).havingCause()
.withMessageContaining("Unable to deduce");
}
}
@Test
void importMultipleTypes() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.name=spring",
"test.value=boot");
context.register(ImportMultipleTypesConfig.class);
context.refresh();
assertThat(context.getBean(JavaBean.class).getName()).isEqualTo("spring");
assertThat(context.getBean(ValueObject.class).getValue()).isEqualTo("boot");
}
}
@Test
void importRepeatedAnnotations() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "jb.name=spring",
"vo.value=boot");
context.register(ImportRepeatedAnnotationsConfig.class);
context.refresh();
assertThat(context.getBean(JavaBean.class).getName()).isEqualTo("spring");
assertThat(context.getBean(ValueObject.class).getValue()).isEqualTo("boot");
}
}
@Test
void importAnnoatedBeanConfig() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "onbean.name=spring");
context.register(ImportAnnotatedClassConfig.class);
context.refresh();
assertThat(context.getBean(JavaBean.class).getName()).isEqualTo("spring");
}
}
@Configuration
@ImportAsConfigurationPropertiesBean(prefix = "test", type = JavaBean.class)
static class JavaBeanConfig {
}
static class JavaBean {
private String name;
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
}
@Configuration
@ImportAsConfigurationPropertiesBean(prefix = "test", type = ValueObject.class)
static class ValueObjectConfig {
}
static class ValueObject {
private final String value;
ValueObject(String value) {
this.value = value;
}
String getValue() {
return this.value;
}
}
@Configuration
@ImportAsConfigurationPropertiesBean(prefix = "test", type = MultiConstructorValueObject.class)
static class MultiConstructorValueObjectConfig {
}
static class MultiConstructorValueObject {
MultiConstructorValueObject() {
}
MultiConstructorValueObject(String name) {
}
MultiConstructorValueObject(String name, int age) {
}
}
@Configuration
@ImportAsConfigurationPropertiesBean(type = { ValueObject.class, JavaBean.class }, prefix = "test")
static class ImportMultipleTypesConfig {
}
@Configuration
@ImportAsConfigurationPropertiesBean(type = ValueObject.class, prefix = "vo")
@ImportAsConfigurationPropertiesBean(type = JavaBean.class, prefix = "jb")
static class ImportRepeatedAnnotationsConfig {
}
@Configuration
@ImportAsConfigurationPropertiesBean(AnnotatedJavaBean.class)
static class ImportAnnotatedClassConfig {
}
@ConfigurationProperties(prefix = "onbean")
static class AnnotatedJavaBean extends JavaBean {
}
}
...@@ -139,19 +139,6 @@ class BindableTests { ...@@ -139,19 +139,6 @@ class BindableTests {
assertThat(Bindable.of(String.class).withAnnotations(annotation).getAnnotation(Bean.class)).isNull(); assertThat(Bindable.of(String.class).withAnnotations(annotation).getAnnotation(Bean.class)).isNull();
} }
@Test
void withAttributeShouldSetAttribute() {
Bindable<String> bindable = Bindable.of(String.class);
Bindable<String> withOne = bindable.withAttribute("one", 1);
Bindable<String> withOneAndTwo = withOne.withAttribute("two", 2);
assertThat(bindable.getAttribute("one")).isNull();
assertThat(bindable.getAttribute("two")).isNull();
assertThat(withOne.getAttribute("one")).isEqualTo(1);
assertThat(withOne.getAttribute("two")).isNull();
assertThat(withOneAndTwo.getAttribute("one")).isEqualTo(1);
assertThat(withOneAndTwo.getAttribute("two")).isEqualTo(2);
}
@Test @Test
void toStringShouldShowDetails() { void toStringShouldShowDetails() {
Annotation annotation = AnnotationUtils.synthesizeAnnotation(TestAnnotation.class); Annotation annotation = AnnotationUtils.synthesizeAnnotation(TestAnnotation.class);
...@@ -167,10 +154,9 @@ class BindableTests { ...@@ -167,10 +154,9 @@ class BindableTests {
Bindable<String> bindable1 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation); Bindable<String> bindable1 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation);
Bindable<String> bindable2 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation); Bindable<String> bindable2 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation);
Bindable<String> bindable3 = Bindable.of(String.class).withExistingValue("fof").withAnnotations(annotation); Bindable<String> bindable3 = Bindable.of(String.class).withExistingValue("fof").withAnnotations(annotation);
Bindable<String> bindable4 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation)
.withAttribute("bar", "bar");
assertThat(bindable1.hashCode()).isEqualTo(bindable2.hashCode()); assertThat(bindable1.hashCode()).isEqualTo(bindable2.hashCode());
assertThat(bindable1).isEqualTo(bindable1).isEqualTo(bindable2).isEqualTo(bindable3).isNotEqualTo(bindable4); assertThat(bindable1).isEqualTo(bindable1).isEqualTo(bindable2);
assertThat(bindable1).isEqualTo(bindable3);
} }
@Test // gh-18218 @Test // gh-18218
......
...@@ -3,6 +3,7 @@ package org.springframework.boot.context.properties ...@@ -3,6 +3,7 @@ package org.springframework.boot.context.properties
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.beans.factory.support.DefaultListableBeanFactory import org.springframework.beans.factory.support.DefaultListableBeanFactory
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.core.type.AnnotationMetadata import org.springframework.core.type.AnnotationMetadata
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory import org.springframework.core.type.classreading.SimpleMetadataReaderFactory
...@@ -20,15 +21,15 @@ class KotlinConfigurationPropertiesBeanRegistrarTests { ...@@ -20,15 +21,15 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
@Test @Test
fun `type with default constructor should register generic bean definition`() { fun `type with default constructor should register generic bean definition`() {
this.registrar.register(FooProperties::class.java, null, false) this.registrar.register(FooProperties::class.java)
val beanDefinition = this.beanFactory.getBeanDefinition( val beanDefinition = this.beanFactory.getBeanDefinition(
"foo-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$FooProperties") "foo-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$FooProperties")
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition::class.java) assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
} }
@Test @Test
fun `type with primary constructor and no autowired should register configuration properties bean definition`() { fun `type with primary constructor and no autowired should register configuration properties bean definition`() {
this.registrar.register(BarProperties::class.java, null, false) this.registrar.register(BarProperties::class.java)
val beanDefinition = this.beanFactory.getBeanDefinition( val beanDefinition = this.beanFactory.getBeanDefinition(
"bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties") "bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties")
assertThat(beanDefinition).isExactlyInstanceOf( assertThat(beanDefinition).isExactlyInstanceOf(
...@@ -37,10 +38,10 @@ class KotlinConfigurationPropertiesBeanRegistrarTests { ...@@ -37,10 +38,10 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
@Test @Test
fun `type with no primary constructor should register generic bean definition`() { fun `type with no primary constructor should register generic bean definition`() {
this.registrar.register(BingProperties::class.java, null, false) this.registrar.register(BingProperties::class.java)
val beanDefinition = this.beanFactory.getBeanDefinition( val beanDefinition = this.beanFactory.getBeanDefinition(
"bing-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BingProperties") "bing-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BingProperties")
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition::class.java) assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
} }
@ConfigurationProperties(prefix = "foo") @ConfigurationProperties(prefix = "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