Add metadata support for types in arbitrary modules
Previously, if a ConfigurationProperties had a nested type or was
extending from a type located outside the compilation unit, no
metadata discovered on the source code was available (documentation and
explicit default value, if any). This typically happens when such a type
resides in another module.
This commit introduces `@ConfigurationPropertiesSource` as a way to
annotate such type and have metadata generated for them in their own
module.
Type-metadata is generated as one file per type and is reused
transparently whenever that type is used. As for module metadata, an
additional file can be crafted manually and will be merged when the
metadata for the type is generated.
The following is an example structure with two types where one has
an additional metadata:
META-iNF/
spring/
configuration-properties/
additional/
com.example.SourceOne.json
com.example.SourceOne.json
com.example.SourceTwo.json
Those files are used only by the annotation processor and are not meant
to be public API.
See gh-18366
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.Duration;
|
||||
@@ -28,6 +27,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
@@ -48,6 +48,7 @@ import javax.tools.Diagnostic.Kind;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.InvalidConfigurationMetadataException;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemIgnore;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
|
||||
@@ -64,6 +65,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@SupportedAnnotationTypes({ ConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
|
||||
ConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_SOURCE_ANNOTATION,
|
||||
ConfigurationMetadataAnnotationProcessor.AUTO_CONFIGURATION_ANNOTATION,
|
||||
ConfigurationMetadataAnnotationProcessor.CONFIGURATION_ANNOTATION,
|
||||
ConfigurationMetadataAnnotationProcessor.CONTROLLER_ENDPOINT_ANNOTATION,
|
||||
@@ -78,6 +80,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
|
||||
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.context.properties.ConfigurationProperties";
|
||||
|
||||
static final String CONFIGURATION_PROPERTIES_SOURCE_ANNOTATION = "org.springframework.boot.context.properties.ConfigurationPropertiesSource";
|
||||
|
||||
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.context.properties.NestedConfigurationProperty";
|
||||
|
||||
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.context.properties.DeprecatedConfigurationProperty";
|
||||
@@ -116,6 +120,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
|
||||
private MetadataStore metadataStore;
|
||||
|
||||
private MetadataCollectors metadataCollectors;
|
||||
|
||||
private MetadataCollector metadataCollector;
|
||||
|
||||
private MetadataGenerationEnvironment metadataEnv;
|
||||
@@ -124,6 +130,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
return CONFIGURATION_PROPERTIES_ANNOTATION;
|
||||
}
|
||||
|
||||
protected String configurationPropertiesSourceAnnotation() {
|
||||
return CONFIGURATION_PROPERTIES_SOURCE_ANNOTATION;
|
||||
}
|
||||
|
||||
protected String nestedConfigurationPropertyAnnotation() {
|
||||
return NESTED_CONFIGURATION_PROPERTY_ANNOTATION;
|
||||
}
|
||||
@@ -178,23 +188,35 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment env) {
|
||||
super.init(env);
|
||||
this.metadataStore = new MetadataStore(env);
|
||||
this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata());
|
||||
TypeUtils typeUtils = new TypeUtils(env);
|
||||
this.metadataStore = new MetadataStore(env, typeUtils);
|
||||
this.metadataCollectors = new MetadataCollectors(env, typeUtils);
|
||||
this.metadataCollector = this.metadataCollectors.getModuleMetadataCollector();
|
||||
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
|
||||
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
|
||||
constructorBindingAnnotation(), autowiredAnnotation(), defaultValueAnnotation(), endpointAnnotations(),
|
||||
readOperationAnnotation(), optionalParameterAnnotation(), nameAnnotation());
|
||||
configurationPropertiesSourceAnnotation(), nestedConfigurationPropertyAnnotation(),
|
||||
deprecatedConfigurationPropertyAnnotation(), constructorBindingAnnotation(), autowiredAnnotation(),
|
||||
defaultValueAnnotation(), endpointAnnotations(), readOperationAnnotation(),
|
||||
optionalParameterAnnotation(), nameAnnotation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
this.metadataCollector.processing(roundEnv);
|
||||
this.metadataCollectors.processing(roundEnv);
|
||||
TypeElement annotationType = this.metadataEnv.getConfigurationPropertiesAnnotationElement();
|
||||
if (annotationType != null) { // Is @ConfigurationProperties available
|
||||
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
|
||||
processElement(element);
|
||||
}
|
||||
}
|
||||
TypeElement sourceAnnotationType = this.metadataEnv.getConfigurationPropertiesSourceAnnotationElement();
|
||||
if (sourceAnnotationType != null) { // Is @ConfigurationPropertiesSource available
|
||||
for (Element element : roundEnv.getElementsAnnotatedWith(sourceAnnotationType)) {
|
||||
if (element instanceof TypeElement typeElement) {
|
||||
MetadataCollector metadataCollector = this.metadataCollectors.getMetadataCollector(typeElement);
|
||||
processSourceElement(metadataCollector, "", typeElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<TypeElement> endpointTypes = this.metadataEnv.getEndpointAnnotationElements();
|
||||
if (!endpointTypes.isEmpty()) { // Are endpoint annotations available
|
||||
for (TypeElement endpointType : endpointTypes) {
|
||||
@@ -203,6 +225,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
}
|
||||
if (roundEnv.processingOver()) {
|
||||
try {
|
||||
writeSourceMetadata();
|
||||
writeMetadata();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -276,6 +299,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
seen.push(element);
|
||||
new PropertyDescriptorResolver(this.metadataEnv).resolve(element, source).forEach((descriptor) -> {
|
||||
this.metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv));
|
||||
ItemHint itemHint = descriptor.resolveItemHint(prefix, this.metadataEnv);
|
||||
if (itemHint != null) {
|
||||
this.metadataCollector.add(itemHint);
|
||||
}
|
||||
if (descriptor.isNested(this.metadataEnv)) {
|
||||
TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils()
|
||||
.asElement(descriptor.getType());
|
||||
@@ -287,6 +314,18 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
}
|
||||
}
|
||||
|
||||
private void processSourceElement(MetadataCollector metadataCollector, String prefix, TypeElement element) {
|
||||
new PropertyDescriptorResolver(this.metadataEnv).resolve(element, null).forEach((descriptor) -> {
|
||||
metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv));
|
||||
if (descriptor.isNested(this.metadataEnv)) {
|
||||
TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils()
|
||||
.asElement(descriptor.getType());
|
||||
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, descriptor.getName());
|
||||
processSourceElement(metadataCollector, nestedPrefix, nestedTypeElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void processEndpoint(Element element, List<Element> annotations) {
|
||||
try {
|
||||
String annotationName = this.metadataEnv.getTypeUtils().getQualifiedName(annotations.get(0));
|
||||
@@ -381,9 +420,20 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
return this.metadataEnv.getAnnotationElementStringValue(annotation, "value");
|
||||
}
|
||||
|
||||
protected void writeSourceMetadata() throws Exception {
|
||||
for (TypeElement sourceType : this.metadataCollectors.getSourceTypes()) {
|
||||
ConfigurationMetadata metadata = this.metadataCollectors.getMetadataCollector(sourceType).getMetadata();
|
||||
metadata = mergeAdditionalMetadata(metadata, () -> this.metadataStore.readAdditionalMetadata(sourceType));
|
||||
removeIgnored(metadata);
|
||||
if (!metadata.getItems().isEmpty()) {
|
||||
this.metadataStore.writeMetadata(metadata, sourceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ConfigurationMetadata writeMetadata() throws Exception {
|
||||
ConfigurationMetadata metadata = this.metadataCollector.getMetadata();
|
||||
metadata = mergeAdditionalMetadata(metadata);
|
||||
metadata = mergeAdditionalMetadata(metadata, () -> this.metadataStore.readAdditionalMetadata());
|
||||
removeIgnored(metadata);
|
||||
if (!metadata.getItems().isEmpty()) {
|
||||
this.metadataStore.writeMetadata(metadata);
|
||||
@@ -398,14 +448,16 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigurationMetadata mergeAdditionalMetadata(ConfigurationMetadata metadata) {
|
||||
private ConfigurationMetadata mergeAdditionalMetadata(ConfigurationMetadata metadata,
|
||||
Supplier<ConfigurationMetadata> additionalMetadataSupplier) {
|
||||
try {
|
||||
ConfigurationMetadata merged = new ConfigurationMetadata(metadata);
|
||||
merged.merge(this.metadataStore.readAdditionalMetadata());
|
||||
return merged;
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
// No additional metadata
|
||||
ConfigurationMetadata additionalMetadata = additionalMetadataSupplier.get();
|
||||
if (additionalMetadata != null) {
|
||||
ConfigurationMetadata merged = new ConfigurationMetadata(metadata);
|
||||
merged.merge(additionalMetadata);
|
||||
return merged;
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
catch (InvalidConfigurationMetadataException ex) {
|
||||
log(ex.getKind(), ex.getMessage());
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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 java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
|
||||
import org.springframework.boot.configurationprocessor.support.ConventionUtils;
|
||||
|
||||
/**
|
||||
* Resolve source configuration metadata for arbitrary types.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ConfigurationPropertiesSourceResolver {
|
||||
|
||||
private final ProcessingEnvironment processingEnvironment;
|
||||
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
ConfigurationPropertiesSourceResolver(ProcessingEnvironment processingEnvironment, TypeUtils typeUtils) {
|
||||
this.typeUtils = typeUtils;
|
||||
this.processingEnvironment = processingEnvironment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@link SourceMetadata} for the specified type. If the type has no
|
||||
* source metadata, return an {@link SourceMetadata#EMPTY} source.
|
||||
* @param typeElement the type to discover source metadata from
|
||||
* @return the source metadata for the specified type
|
||||
*/
|
||||
SourceMetadata resolveSource(TypeElement typeElement) {
|
||||
ConfigurationMetadata configurationMetadata = resolveConfigurationMetadata(typeElement);
|
||||
return (configurationMetadata != null)
|
||||
? new SourceMetadata(configurationMetadata.getItems(), configurationMetadata.getHints())
|
||||
: SourceMetadata.EMPTY;
|
||||
}
|
||||
|
||||
private ConfigurationMetadata resolveConfigurationMetadata(TypeElement type) {
|
||||
try {
|
||||
String sourceLocation = MetadataStore.SOURCE_METADATA_PATH.apply(type, this.typeUtils);
|
||||
FileObject resource = this.processingEnvironment.getFiler()
|
||||
.getResource(StandardLocation.CLASS_PATH, "", sourceLocation);
|
||||
return (resource != null) ? new JsonMarshaller().read(resource.openInputStream()) : null;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional source of metadata.
|
||||
*/
|
||||
static final class SourceMetadata {
|
||||
|
||||
/**
|
||||
* An empty source metadata.
|
||||
*/
|
||||
public static final SourceMetadata EMPTY = new SourceMetadata(Collections.emptyList(), Collections.emptyList());
|
||||
|
||||
private final Map<String, ItemMetadata> items;
|
||||
|
||||
private final Map<String, ItemHint> hints;
|
||||
|
||||
private SourceMetadata(List<ItemMetadata> items, List<ItemHint> hints) {
|
||||
this.items = items.stream()
|
||||
.collect(Collectors.toMap((item) -> ConventionUtils.toDashedCase(item.getName()), Function.identity()));
|
||||
this.hints = hints.stream()
|
||||
.collect(Collectors.toMap((item) -> ConventionUtils.toDashedCase(item.getName()), Function.identity()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PropertyDescriptor} for the given property name.
|
||||
* @param name the name of a property
|
||||
* @param propertyDescriptor the descriptor of the property
|
||||
* @return a property descriptor that applies additional source metadata if
|
||||
* necessary
|
||||
*/
|
||||
PropertyDescriptor createPropertyDescriptor(String name, PropertyDescriptor propertyDescriptor) {
|
||||
String key = ConventionUtils.toDashedCase(name);
|
||||
if (this.items.containsKey(key)) {
|
||||
ItemMetadata itemMetadata = this.items.get(key);
|
||||
ItemHint itemHint = this.hints.get(key);
|
||||
return new SourcePropertyDescriptor(propertyDescriptor, itemMetadata, itemHint);
|
||||
}
|
||||
return propertyDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PropertyDescriptor} for the given property name.
|
||||
* @param name the name of a property
|
||||
* @param regularDescriptor a function to get the descriptor
|
||||
* @return a property descriptor that applies additional source metadata if
|
||||
* necessary
|
||||
*/
|
||||
PropertyDescriptor createPropertyDescriptor(String name,
|
||||
Function<String, PropertyDescriptor> regularDescriptor) {
|
||||
return createPropertyDescriptor(name, regularDescriptor.apply(name));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} that applies source metadata.
|
||||
*/
|
||||
static class SourcePropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
private final PropertyDescriptor delegate;
|
||||
|
||||
private final ItemMetadata sourceItemMetadata;
|
||||
|
||||
private final ItemHint sourceItemHint;
|
||||
|
||||
SourcePropertyDescriptor(PropertyDescriptor delegate, ItemMetadata sourceItemMetadata,
|
||||
ItemHint sourceItemHint) {
|
||||
super(delegate.getName(), delegate.getType(), delegate.getDeclaringElement(), delegate.getGetter());
|
||||
this.delegate = delegate;
|
||||
this.sourceItemMetadata = sourceItemMetadata;
|
||||
this.sourceItemHint = sourceItemHint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemHint resolveItemHint(String prefix, MetadataGenerationEnvironment environment) {
|
||||
return (this.sourceItemHint != null) ? this.sourceItemHint.applyPrefix(prefix)
|
||||
: super.resolveItemHint(prefix, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
|
||||
return this.delegate.isMarkedAsNested(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveDescription(MetadataGenerationEnvironment environment) {
|
||||
String description = this.delegate.resolveDescription(environment);
|
||||
return (description != null) ? description : this.sourceItemMetadata.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||
Object defaultValue = this.delegate.resolveDefaultValue(environment);
|
||||
return (defaultValue != null) ? defaultValue : this.sourceItemMetadata.getDefaultValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
|
||||
ItemDeprecation itemDeprecation = this.delegate.resolveItemDeprecation(environment);
|
||||
return (itemDeprecation != null) ? itemDeprecation : this.sourceItemMetadata.getDeprecation();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isProperty(MetadataGenerationEnvironment environment) {
|
||||
return this.delegate.isProperty(environment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,15 +16,13 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} for a constructor parameter.
|
||||
*
|
||||
@@ -45,8 +43,8 @@ class ConstructorParameterPropertyDescriptor extends ParameterPropertyDescriptor
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Element> getDeprecatableElements() {
|
||||
return Arrays.asList(getGetter(), this.setter, this.field);
|
||||
protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
|
||||
return resolveItemDeprecation(environment, getGetter(), this.setter, this.field);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,15 +16,13 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} for a standard JavaBean property.
|
||||
*
|
||||
@@ -68,8 +66,8 @@ class JavaBeanPropertyDescriptor extends PropertyDescriptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Element> getDeprecatableElements() {
|
||||
return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod);
|
||||
protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
|
||||
return resolveItemDeprecation(environment, getGetter(), this.setter, this.field, this.factoryMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,18 +16,17 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} for a Lombok field.
|
||||
*
|
||||
@@ -80,8 +79,8 @@ class LombokPropertyDescriptor extends PropertyDescriptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Element> getDeprecatableElements() {
|
||||
return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod);
|
||||
protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
|
||||
return resolveItemDeprecation(environment, getGetter(), this.setter, this.field, this.factoryMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,18 +16,14 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
|
||||
/**
|
||||
@@ -37,48 +33,33 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
* @author Andy Wilkinson
|
||||
* @author Kris De Volder
|
||||
* @author Moritz Halbritter
|
||||
* @since 1.2.2
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class MetadataCollector {
|
||||
class MetadataCollector {
|
||||
|
||||
private final Set<ItemMetadata> metadataItems = new LinkedHashSet<>();
|
||||
|
||||
private final ProcessingEnvironment processingEnvironment;
|
||||
private final Predicate<ItemMetadata> mergeRequired;
|
||||
|
||||
private final ConfigurationMetadata previousMetadata;
|
||||
|
||||
private final TypeUtils typeUtils;
|
||||
private final Set<ItemMetadata> metadataItems = new LinkedHashSet<>();
|
||||
|
||||
private final Set<String> processedSourceTypes = new HashSet<>();
|
||||
private final Set<ItemHint> metadataHints = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@code MetadataProcessor} instance.
|
||||
* @param processingEnvironment the processing environment of the build
|
||||
* @param mergeRequired specify whether an item can be merged
|
||||
* @param previousMetadata any previous metadata or {@code null}
|
||||
*/
|
||||
public MetadataCollector(ProcessingEnvironment processingEnvironment, ConfigurationMetadata previousMetadata) {
|
||||
this.processingEnvironment = processingEnvironment;
|
||||
MetadataCollector(Predicate<ItemMetadata> mergeRequired, ConfigurationMetadata previousMetadata) {
|
||||
this.mergeRequired = mergeRequired;
|
||||
this.previousMetadata = previousMetadata;
|
||||
this.typeUtils = new TypeUtils(processingEnvironment);
|
||||
}
|
||||
|
||||
public void processing(RoundEnvironment roundEnv) {
|
||||
for (Element element : roundEnv.getRootElements()) {
|
||||
markAsProcessed(element);
|
||||
}
|
||||
}
|
||||
|
||||
private void markAsProcessed(Element element) {
|
||||
if (element instanceof TypeElement) {
|
||||
this.processedSourceTypes.add(this.typeUtils.getQualifiedName(element));
|
||||
}
|
||||
}
|
||||
|
||||
public void add(ItemMetadata metadata) {
|
||||
void add(ItemMetadata metadata) {
|
||||
this.metadataItems.add(metadata);
|
||||
}
|
||||
|
||||
public void add(ItemMetadata metadata, Consumer<ItemMetadata> onConflict) {
|
||||
void add(ItemMetadata metadata, Consumer<ItemMetadata> onConflict) {
|
||||
ItemMetadata existing = find(metadata.getName());
|
||||
if (existing != null) {
|
||||
onConflict.accept(existing);
|
||||
@@ -87,7 +68,7 @@ public class MetadataCollector {
|
||||
add(metadata);
|
||||
}
|
||||
|
||||
public boolean addIfAbsent(ItemMetadata metadata) {
|
||||
boolean addIfAbsent(ItemMetadata metadata) {
|
||||
ItemMetadata existing = find(metadata.getName());
|
||||
if (existing != null) {
|
||||
return false;
|
||||
@@ -96,7 +77,11 @@ public class MetadataCollector {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasSimilarGroup(ItemMetadata metadata) {
|
||||
void add(ItemHint itemHint) {
|
||||
this.metadataHints.add(itemHint);
|
||||
}
|
||||
|
||||
boolean hasSimilarGroup(ItemMetadata metadata) {
|
||||
if (!metadata.isOfItemType(ItemMetadata.ItemType.GROUP)) {
|
||||
throw new IllegalStateException("item " + metadata + " must be a group");
|
||||
}
|
||||
@@ -109,15 +94,18 @@ public class MetadataCollector {
|
||||
return false;
|
||||
}
|
||||
|
||||
public ConfigurationMetadata getMetadata() {
|
||||
ConfigurationMetadata getMetadata() {
|
||||
ConfigurationMetadata metadata = new ConfigurationMetadata();
|
||||
for (ItemMetadata item : this.metadataItems) {
|
||||
metadata.add(item);
|
||||
}
|
||||
for (ItemHint metadataHint : this.metadataHints) {
|
||||
metadata.add(metadataHint);
|
||||
}
|
||||
if (this.previousMetadata != null) {
|
||||
List<ItemMetadata> items = this.previousMetadata.getItems();
|
||||
for (ItemMetadata item : items) {
|
||||
if (shouldBeMerged(item)) {
|
||||
if (this.mergeRequired.test(item)) {
|
||||
metadata.addIfMissing(item);
|
||||
}
|
||||
}
|
||||
@@ -132,17 +120,4 @@ public class MetadataCollector {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private boolean shouldBeMerged(ItemMetadata itemMetadata) {
|
||||
String sourceType = itemMetadata.getSourceType();
|
||||
return (sourceType != null && !deletedInCurrentBuild(sourceType) && !processedInCurrentBuild(sourceType));
|
||||
}
|
||||
|
||||
private boolean deletedInCurrentBuild(String sourceType) {
|
||||
return this.processingEnvironment.getElementUtils().getTypeElement(sourceType.replace('$', '.')) == null;
|
||||
}
|
||||
|
||||
private boolean processedInCurrentBuild(String sourceType) {
|
||||
return this.processedSourceTypes.contains(sourceType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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 java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
|
||||
/**
|
||||
* Container for {@link MetadataCollector}. Usually, either metadata for the whole module
|
||||
* or metadata for types is generated. This makes sure to record types that have been
|
||||
* processed and determine if previous metadata should be merged.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class MetadataCollectors {
|
||||
|
||||
private final ProcessingEnvironment processingEnvironment;
|
||||
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
private final MetadataStore metadataStore;
|
||||
|
||||
private final MetadataCollector metadataCollector;
|
||||
|
||||
private final Set<String> processedSourceTypes = new HashSet<>();
|
||||
|
||||
private final Map<TypeElement, MetadataCollector> metadataTypeCollectors = new HashMap<>();
|
||||
|
||||
MetadataCollectors(ProcessingEnvironment processingEnvironment, TypeUtils typeUtils) {
|
||||
this.processingEnvironment = processingEnvironment;
|
||||
this.typeUtils = typeUtils;
|
||||
this.metadataStore = new MetadataStore(this.processingEnvironment, this.typeUtils);
|
||||
this.metadataCollector = new MetadataCollector(this::shouldBeMerged, this.metadataStore.readMetadata());
|
||||
}
|
||||
|
||||
void processing(RoundEnvironment roundEnv) {
|
||||
for (Element element : roundEnv.getRootElements()) {
|
||||
if (element instanceof TypeElement) {
|
||||
this.processedSourceTypes.add(this.typeUtils.getQualifiedName(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetadataCollector getModuleMetadataCollector() {
|
||||
return this.metadataCollector;
|
||||
}
|
||||
|
||||
MetadataCollector getMetadataCollector(TypeElement element) {
|
||||
return this.metadataTypeCollectors.computeIfAbsent(element,
|
||||
(ignored) -> new MetadataCollector(this::shouldBeMerged, this.metadataStore.readMetadata(element)));
|
||||
}
|
||||
|
||||
Set<TypeElement> getSourceTypes() {
|
||||
return this.metadataTypeCollectors.keySet();
|
||||
}
|
||||
|
||||
private boolean shouldBeMerged(ItemMetadata itemMetadata) {
|
||||
String sourceType = itemMetadata.getSourceType();
|
||||
return (sourceType != null && !deletedInCurrentBuild(sourceType) && !processedInCurrentBuild(sourceType));
|
||||
}
|
||||
|
||||
private boolean deletedInCurrentBuild(String sourceType) {
|
||||
return this.processingEnvironment.getElementUtils().getTypeElement(sourceType.replace('$', '.')) == null;
|
||||
}
|
||||
|
||||
private boolean processedInCurrentBuild(String sourceType) {
|
||||
return this.processedSourceTypes.contains(sourceType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.ConfigurationPropertiesSourceResolver.SourceMetadata;
|
||||
import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser;
|
||||
import org.springframework.boot.configurationprocessor.fieldvalues.javac.JavaCompilerFieldValuesParser;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
@@ -75,12 +76,18 @@ class MetadataGenerationEnvironment {
|
||||
|
||||
private final FieldValuesParser fieldValuesParser;
|
||||
|
||||
private final ConfigurationPropertiesSourceResolver sourceResolver;
|
||||
|
||||
private final Map<TypeElement, Map<String, Object>> defaultValues = new HashMap<>();
|
||||
|
||||
private final Map<TypeElement, SourceMetadata> sources = new HashMap<>();
|
||||
|
||||
private final String configurationPropertiesAnnotation;
|
||||
|
||||
private final String nestedConfigurationPropertyAnnotation;
|
||||
|
||||
private final String configurationPropertiesSourceAnnotation;
|
||||
|
||||
private final String deprecatedConfigurationPropertyAnnotation;
|
||||
|
||||
private final String constructorBindingAnnotation;
|
||||
@@ -98,15 +105,17 @@ class MetadataGenerationEnvironment {
|
||||
private final String autowiredAnnotation;
|
||||
|
||||
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
|
||||
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
|
||||
String constructorBindingAnnotation, String autowiredAnnotation, String defaultValueAnnotation,
|
||||
Set<String> endpointAnnotations, String readOperationAnnotation, String optionalParameterAnnotation,
|
||||
String nameAnnotation) {
|
||||
String configurationPropertiesSourceAnnotation, String nestedConfigurationPropertyAnnotation,
|
||||
String deprecatedConfigurationPropertyAnnotation, String constructorBindingAnnotation,
|
||||
String autowiredAnnotation, String defaultValueAnnotation, Set<String> endpointAnnotations,
|
||||
String readOperationAnnotation, String optionalParameterAnnotation, String nameAnnotation) {
|
||||
this.typeUtils = new TypeUtils(environment);
|
||||
this.elements = environment.getElementUtils();
|
||||
this.messager = environment.getMessager();
|
||||
this.fieldValuesParser = resolveFieldValuesParser(environment);
|
||||
this.sourceResolver = new ConfigurationPropertiesSourceResolver(environment, this.typeUtils);
|
||||
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
|
||||
this.configurationPropertiesSourceAnnotation = configurationPropertiesSourceAnnotation;
|
||||
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
|
||||
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
|
||||
this.constructorBindingAnnotation = constructorBindingAnnotation;
|
||||
@@ -146,6 +155,22 @@ class MetadataGenerationEnvironment {
|
||||
return this.defaultValues.computeIfAbsent(type, this::resolveFieldValues).get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@link SourceMetadata} for the specified property.
|
||||
* @param field the field of the property (can be {@code null})
|
||||
* @param getter the getter of the property (can be {@code null})
|
||||
* @return the {@link SourceMetadata} for the specified property
|
||||
*/
|
||||
SourceMetadata resolveSourceMetadata(VariableElement field, ExecutableElement getter) {
|
||||
if (field != null && field.getEnclosingElement() instanceof TypeElement type) {
|
||||
return this.sources.computeIfAbsent(type, this.sourceResolver::resolveSource);
|
||||
}
|
||||
if (getter != null && getter.getEnclosingElement() instanceof TypeElement type) {
|
||||
return this.sources.computeIfAbsent(type, this.sourceResolver::resolveSource);
|
||||
}
|
||||
return SourceMetadata.EMPTY;
|
||||
}
|
||||
|
||||
boolean isExcluded(TypeMirror type) {
|
||||
if (type == null) {
|
||||
return false;
|
||||
@@ -314,6 +339,10 @@ class MetadataGenerationEnvironment {
|
||||
return getAnnotation(element, this.configurationPropertiesAnnotation);
|
||||
}
|
||||
|
||||
TypeElement getConfigurationPropertiesSourceAnnotationElement() {
|
||||
return this.elements.getTypeElement(this.configurationPropertiesSourceAnnotation);
|
||||
}
|
||||
|
||||
AnnotationMirror getNestedConfigurationPropertyAnnotation(Element element) {
|
||||
return getAnnotation(element, this.nestedConfigurationPropertyAnnotation);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
@@ -37,46 +39,123 @@ import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
* @since 1.2.2
|
||||
*/
|
||||
public class MetadataStore {
|
||||
class MetadataStore {
|
||||
|
||||
static final String METADATA_PATH = "META-INF/spring-configuration-metadata.json";
|
||||
|
||||
static final BiFunction<TypeElement, TypeUtils, String> SOURCE_METADATA_PATH = (type,
|
||||
typeUtils) -> "META-INF/spring/configuration-metadata/%s.json".formatted(typeUtils.getQualifiedName(type));
|
||||
|
||||
private static final String ADDITIONAL_METADATA_PATH = "META-INF/additional-spring-configuration-metadata.json";
|
||||
|
||||
static final BiFunction<TypeElement, TypeUtils, String> ADDITIONAL_SOURCE_METADATA_PATH = (type,
|
||||
typeUtils) -> "META-INF/spring/configuration-metadata/additional/%s.json"
|
||||
.formatted(typeUtils.getQualifiedName(type));
|
||||
|
||||
private static final String RESOURCES_DIRECTORY = "resources";
|
||||
|
||||
private static final String CLASSES_DIRECTORY = "classes";
|
||||
|
||||
private final ProcessingEnvironment environment;
|
||||
|
||||
public MetadataStore(ProcessingEnvironment environment) {
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
MetadataStore(ProcessingEnvironment environment, TypeUtils typeUtils) {
|
||||
this.environment = environment;
|
||||
this.typeUtils = typeUtils;
|
||||
}
|
||||
|
||||
public ConfigurationMetadata readMetadata() {
|
||||
/**
|
||||
* Read the existing {@link ConfigurationMetadata} of the current module or
|
||||
* {@code null} if it is not available yet.
|
||||
* @return the metadata or {@code null} if none is present
|
||||
*/
|
||||
ConfigurationMetadata readMetadata() {
|
||||
return readMetadata(METADATA_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the existing {@link ConfigurationMetadata} for the specified type or
|
||||
* {@code null} if it is not available yet.
|
||||
* @param typeElement the type to read metadata for
|
||||
* @return the metadata for the given type or {@code null}
|
||||
*/
|
||||
ConfigurationMetadata readMetadata(TypeElement typeElement) {
|
||||
return readMetadata(SOURCE_METADATA_PATH.apply(typeElement, this.typeUtils));
|
||||
}
|
||||
|
||||
private ConfigurationMetadata readMetadata(String location) {
|
||||
try {
|
||||
return readMetadata(getMetadataResource().openInputStream());
|
||||
return readMetadata(location, getMetadataResource(location).openInputStream());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void writeMetadata(ConfigurationMetadata metadata) throws IOException {
|
||||
/**
|
||||
* Write the module {@link ConfigurationMetadata} to the filesystem.
|
||||
* @param metadata the metadata to write
|
||||
*/
|
||||
void writeMetadata(ConfigurationMetadata metadata) throws IOException {
|
||||
writeMetadata(metadata, () -> createMetadataResource(METADATA_PATH));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the {@link ConfigurationMetadata} for the {@link TypeElement} to the
|
||||
* filesystem.
|
||||
* @param metadata the metadata to write
|
||||
* @param typeElement the type to write metadata for
|
||||
*/
|
||||
void writeMetadata(ConfigurationMetadata metadata, TypeElement typeElement) throws IOException {
|
||||
writeMetadata(metadata, () -> createMetadataResource(SOURCE_METADATA_PATH.apply(typeElement, this.typeUtils)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the metadata to the {@link FileObject} provided by the given supplier.
|
||||
* @param metadata the metadata to provide
|
||||
* @param fileObjectProvider a supplier for the {@link FileObject} to use
|
||||
*/
|
||||
private void writeMetadata(ConfigurationMetadata metadata, FileObjectSupplier fileObjectProvider)
|
||||
throws IOException {
|
||||
if (!metadata.getItems().isEmpty()) {
|
||||
try (OutputStream outputStream = createMetadataResource().openOutputStream()) {
|
||||
try (OutputStream outputStream = fileObjectProvider.get().openOutputStream()) {
|
||||
new JsonMarshaller().write(metadata, outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigurationMetadata readAdditionalMetadata() throws IOException {
|
||||
return readMetadata(getAdditionalMetadataStream());
|
||||
/**
|
||||
* Read additional {@link ConfigurationMetadata} for the current module or
|
||||
* {@code null}.
|
||||
* @return additional metadata or {@code null} if none is present
|
||||
*/
|
||||
ConfigurationMetadata readAdditionalMetadata() {
|
||||
return readAdditionalMetadata(ADDITIONAL_METADATA_PATH);
|
||||
}
|
||||
|
||||
private ConfigurationMetadata readMetadata(InputStream in) {
|
||||
/**
|
||||
* Read additional {@link ConfigurationMetadata} for the {@link TypeElement} or
|
||||
* {@code null}.
|
||||
* @param typeElement the type to get additional metadata for
|
||||
* @return additional metadata for the given type or {@code null} if none is present
|
||||
*/
|
||||
ConfigurationMetadata readAdditionalMetadata(TypeElement typeElement) {
|
||||
return readAdditionalMetadata(ADDITIONAL_SOURCE_METADATA_PATH.apply(typeElement, this.typeUtils));
|
||||
}
|
||||
|
||||
private ConfigurationMetadata readAdditionalMetadata(String location) {
|
||||
try {
|
||||
InputStream in = getAdditionalMetadataStream(location);
|
||||
return readMetadata(location, in);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigurationMetadata readMetadata(String location, InputStream in) {
|
||||
try (in) {
|
||||
return new JsonMarshaller().read(in);
|
||||
}
|
||||
@@ -85,29 +164,28 @@ public class MetadataStore {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new InvalidConfigurationMetadataException(
|
||||
"Invalid additional meta-data in '" + METADATA_PATH + "': " + ex.getMessage(),
|
||||
Diagnostic.Kind.ERROR);
|
||||
"Invalid additional meta-data in '" + location + "': " + ex.getMessage(), Diagnostic.Kind.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private FileObject getMetadataResource() throws IOException {
|
||||
return this.environment.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH);
|
||||
private FileObject getMetadataResource(String location) throws IOException {
|
||||
return this.environment.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", location);
|
||||
}
|
||||
|
||||
private FileObject createMetadataResource() throws IOException {
|
||||
return this.environment.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", METADATA_PATH);
|
||||
private FileObject createMetadataResource(String location) throws IOException {
|
||||
return this.environment.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", location);
|
||||
}
|
||||
|
||||
private InputStream getAdditionalMetadataStream() throws IOException {
|
||||
private InputStream getAdditionalMetadataStream(String additionalMetadataLocation) throws IOException {
|
||||
// Most build systems will have copied the file to the class output location
|
||||
FileObject fileObject = this.environment.getFiler()
|
||||
.getResource(StandardLocation.CLASS_OUTPUT, "", ADDITIONAL_METADATA_PATH);
|
||||
.getResource(StandardLocation.CLASS_OUTPUT, "", additionalMetadataLocation);
|
||||
InputStream inputStream = getMetadataStream(fileObject);
|
||||
if (inputStream != null) {
|
||||
return inputStream;
|
||||
}
|
||||
try {
|
||||
File file = locateAdditionalMetadataFile(new File(fileObject.toUri()));
|
||||
File file = locateAdditionalMetadataFile(new File(fileObject.toUri()), additionalMetadataLocation);
|
||||
return (file.exists() ? new FileInputStream(file) : fileObject.toUri().toURL().openStream());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -124,7 +202,7 @@ public class MetadataStore {
|
||||
}
|
||||
}
|
||||
|
||||
File locateAdditionalMetadataFile(File standardLocation) throws IOException {
|
||||
File locateAdditionalMetadataFile(File standardLocation, String additionalMetadataLocation) throws IOException {
|
||||
if (standardLocation.exists()) {
|
||||
return standardLocation;
|
||||
}
|
||||
@@ -132,13 +210,13 @@ public class MetadataStore {
|
||||
.get(ConfigurationMetadataAnnotationProcessor.ADDITIONAL_METADATA_LOCATIONS_OPTION);
|
||||
if (locations != null) {
|
||||
for (String location : locations.split(",")) {
|
||||
File candidate = new File(location, ADDITIONAL_METADATA_PATH);
|
||||
File candidate = new File(location, additionalMetadataLocation);
|
||||
if (candidate.isFile()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new File(locateGradleResourcesDirectory(standardLocation), ADDITIONAL_METADATA_PATH);
|
||||
return new File(locateGradleResourcesDirectory(standardLocation), additionalMetadataLocation);
|
||||
}
|
||||
|
||||
private File locateGradleResourcesDirectory(File standardAdditionalMetadataLocation) throws FileNotFoundException {
|
||||
@@ -152,4 +230,13 @@ public class MetadataStore {
|
||||
return new File(buildDirectoryPath, RESOURCES_DIRECTORY + '/' + classOutputLocation.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal callback that can throw an {@link IOException}.
|
||||
*/
|
||||
private interface FileObjectSupplier {
|
||||
|
||||
FileObject get() throws IOException;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
@@ -26,6 +26,7 @@ import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
|
||||
/**
|
||||
@@ -106,6 +107,16 @@ abstract class PropertyDescriptor {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@link ItemHint} for this property.
|
||||
* @param prefix the property prefix
|
||||
* @param environment the metadata generation environment
|
||||
* @return the item hint or {@code null}
|
||||
*/
|
||||
protected ItemHint resolveItemHint(String prefix, MetadataGenerationEnvironment environment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if this is a nested property.
|
||||
* @param environment the metadata generation environment
|
||||
@@ -185,13 +196,14 @@ abstract class PropertyDescriptor {
|
||||
deprecation);
|
||||
}
|
||||
|
||||
private String resolveType(MetadataGenerationEnvironment environment) {
|
||||
return environment.getTypeUtils().getType(getDeclaringElement(), getType());
|
||||
protected final ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment,
|
||||
Element... elements) {
|
||||
boolean deprecated = Arrays.stream(elements).anyMatch(environment::isDeprecated);
|
||||
return deprecated ? environment.resolveItemDeprecation(getGetter()) : null;
|
||||
}
|
||||
|
||||
private ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
|
||||
boolean deprecated = getDeprecatableElements().stream().anyMatch(environment::isDeprecated);
|
||||
return deprecated ? environment.resolveItemDeprecation(getGetter()) : null;
|
||||
private String resolveType(MetadataGenerationEnvironment environment) {
|
||||
return environment.getTypeUtils().getType(getDeclaringElement(), getType());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,11 +221,11 @@ abstract class PropertyDescriptor {
|
||||
protected abstract Object resolveDefaultValue(MetadataGenerationEnvironment environment);
|
||||
|
||||
/**
|
||||
* Return all the elements that should be considered when checking for deprecation
|
||||
* annotations.
|
||||
* @return the deprecatable elements
|
||||
* Resolve the {@link ItemDeprecation} for this property.
|
||||
* @param environment the metadata generation environment
|
||||
* @return the deprecation or {@code null}
|
||||
*/
|
||||
protected abstract List<Element> getDeprecatableElements();
|
||||
protected abstract ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment);
|
||||
|
||||
/**
|
||||
* Return true if this descriptor is for a property.
|
||||
|
||||
@@ -32,6 +32,8 @@ import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.ConfigurationPropertiesSourceResolver.SourceMetadata;
|
||||
|
||||
/**
|
||||
* Resolve {@link PropertyDescriptor} instances.
|
||||
*
|
||||
@@ -91,11 +93,13 @@ class PropertyDescriptorResolver {
|
||||
ExecutableElement setter = members.getPublicSetter(name, type);
|
||||
VariableElement field = members.getFields().get(name);
|
||||
RecordComponentElement recordComponent = members.getRecordComponents().get(name);
|
||||
return (recordComponent != null)
|
||||
SourceMetadata sourceMetadata = this.environment.resolveSourceMetadata(field, getter);
|
||||
PropertyDescriptor propertyDescriptor = (recordComponent != null)
|
||||
? new RecordParameterPropertyDescriptor(name, type, parameter, declaringElement, getter,
|
||||
recordComponent)
|
||||
: new ConstructorParameterPropertyDescriptor(name, type, parameter, declaringElement, getter, setter,
|
||||
field);
|
||||
return sourceMetadata.createPropertyDescriptor(name, propertyDescriptor);
|
||||
}
|
||||
|
||||
private String getPropertyName(VariableElement parameter) {
|
||||
@@ -118,16 +122,23 @@ class PropertyDescriptorResolver {
|
||||
VariableElement field = members.getFields().get(name);
|
||||
ExecutableElement getter = findMatchingGetter(members, getters, field);
|
||||
TypeMirror propertyType = getter.getReturnType();
|
||||
register(candidates, new JavaBeanPropertyDescriptor(getPropertyName(field, name), propertyType,
|
||||
declaringElement, getter, members.getPublicSetter(name, propertyType), field, factoryMethod));
|
||||
SourceMetadata sourceMetadata = this.environment.resolveSourceMetadata(field, getter);
|
||||
register(candidates,
|
||||
sourceMetadata.createPropertyDescriptor(getPropertyName(field, name),
|
||||
(propertyName) -> new JavaBeanPropertyDescriptor(propertyName, propertyType,
|
||||
declaringElement, getter, members.getPublicSetter(name, propertyType), field,
|
||||
factoryMethod)));
|
||||
});
|
||||
// Then check for Lombok ones
|
||||
members.getFields().forEach((name, field) -> {
|
||||
TypeMirror propertyType = field.asType();
|
||||
ExecutableElement getter = members.getPublicGetter(name, propertyType);
|
||||
ExecutableElement setter = members.getPublicSetter(name, propertyType);
|
||||
register(candidates, new LombokPropertyDescriptor(getPropertyName(field, name), propertyType,
|
||||
declaringElement, getter, setter, field, factoryMethod));
|
||||
SourceMetadata sourceMetadata = this.environment.resolveSourceMetadata(field, getter);
|
||||
register(candidates,
|
||||
sourceMetadata.createPropertyDescriptor(getPropertyName(field, name),
|
||||
(propertyName) -> new LombokPropertyDescriptor(propertyName, propertyType, declaringElement,
|
||||
getter, setter, field, factoryMethod)));
|
||||
});
|
||||
return candidates.values().stream();
|
||||
}
|
||||
|
||||
@@ -16,16 +16,14 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} for a record parameter.
|
||||
*
|
||||
@@ -44,8 +42,8 @@ class RecordParameterPropertyDescriptor extends ParameterPropertyDescriptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Element> getDeprecatableElements() {
|
||||
return Arrays.asList(getGetter());
|
||||
protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
|
||||
return resolveItemDeprecation(environment, getGetter());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -72,6 +72,16 @@ public class ItemHint implements Comparable<ItemHint> {
|
||||
return Collections.unmodifiableList(this.providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link ItemHint} with the given prefix applied.
|
||||
* @param prefix the prefix to apply
|
||||
* @return a new {@link ItemHint} with the same of this instance whose property name
|
||||
* has the prefix applied to it
|
||||
*/
|
||||
public ItemHint applyPrefix(String prefix) {
|
||||
return new ItemHint(ConventionUtils.toDashedCase(prefix) + "." + this.name, this.values, this.providers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ItemHint other) {
|
||||
return getName().compareTo(other.getName());
|
||||
|
||||
@@ -18,13 +18,19 @@ package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemIgnore;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.Metadata;
|
||||
import org.springframework.boot.configurationprocessor.test.CompiledMetadataReader;
|
||||
import org.springframework.boot.configurationprocessor.test.TestConfigurationMetadataAnnotationProcessor;
|
||||
import org.springframework.boot.configurationsample.deprecation.Dbcp2Configuration;
|
||||
import org.springframework.boot.configurationsample.method.NestedPropertiesMethod;
|
||||
import org.springframework.boot.configurationsample.record.ExampleRecord;
|
||||
@@ -47,6 +53,30 @@ import org.springframework.boot.configurationsample.simple.SimpleCollectionPrope
|
||||
import org.springframework.boot.configurationsample.simple.SimplePrefixValueProperties;
|
||||
import org.springframework.boot.configurationsample.simple.SimpleProperties;
|
||||
import org.springframework.boot.configurationsample.simple.SimpleTypeProperties;
|
||||
import org.springframework.boot.configurationsample.source.ConcreteProperties;
|
||||
import org.springframework.boot.configurationsample.source.ConcreteSource;
|
||||
import org.springframework.boot.configurationsample.source.ConcreteSourceAnnotated;
|
||||
import org.springframework.boot.configurationsample.source.ConventionSource;
|
||||
import org.springframework.boot.configurationsample.source.ConventionSourceAnnotated;
|
||||
import org.springframework.boot.configurationsample.source.ImmutableSource;
|
||||
import org.springframework.boot.configurationsample.source.ImmutableSourceAnnotated;
|
||||
import org.springframework.boot.configurationsample.source.LombokSource;
|
||||
import org.springframework.boot.configurationsample.source.LombokSourceAnnotated;
|
||||
import org.springframework.boot.configurationsample.source.ParentWithHintProperties;
|
||||
import org.springframework.boot.configurationsample.source.RecordSource;
|
||||
import org.springframework.boot.configurationsample.source.RecordSourceAnnotated;
|
||||
import org.springframework.boot.configurationsample.source.SimpleSource;
|
||||
import org.springframework.boot.configurationsample.source.SimpleSourceAnnotated;
|
||||
import org.springframework.boot.configurationsample.source.generation.AbstractPropertiesSource;
|
||||
import org.springframework.boot.configurationsample.source.generation.ConfigurationPropertySourcesContainer;
|
||||
import org.springframework.boot.configurationsample.source.generation.ConfigurationPropertySourcesContainer.First;
|
||||
import org.springframework.boot.configurationsample.source.generation.ConfigurationPropertySourcesContainer.Second;
|
||||
import org.springframework.boot.configurationsample.source.generation.ConfigurationPropertySourcesContainer.Third;
|
||||
import org.springframework.boot.configurationsample.source.generation.ImmutablePropertiesSource;
|
||||
import org.springframework.boot.configurationsample.source.generation.LombokPropertiesSource;
|
||||
import org.springframework.boot.configurationsample.source.generation.NestedPropertiesSource;
|
||||
import org.springframework.boot.configurationsample.source.generation.RecordPropertiesSources;
|
||||
import org.springframework.boot.configurationsample.source.generation.SimplePropertiesSource;
|
||||
import org.springframework.boot.configurationsample.specific.AnnotatedGetter;
|
||||
import org.springframework.boot.configurationsample.specific.BoxingPojo;
|
||||
import org.springframework.boot.configurationsample.specific.BuilderPojo;
|
||||
@@ -69,6 +99,10 @@ import org.springframework.boot.configurationsample.specific.InvalidDoubleRegist
|
||||
import org.springframework.boot.configurationsample.specific.SimplePojo;
|
||||
import org.springframework.boot.configurationsample.specific.StaticAccessor;
|
||||
import org.springframework.core.test.tools.CompilationException;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.ResourceFile;
|
||||
import org.springframework.core.test.tools.SourceFile;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -92,6 +126,7 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
|
||||
assertThat(new ConfigurationMetadataAnnotationProcessor().getSupportedAnnotationTypes())
|
||||
.containsExactlyInAnyOrder("org.springframework.boot.autoconfigure.AutoConfiguration",
|
||||
"org.springframework.boot.context.properties.ConfigurationProperties",
|
||||
"org.springframework.boot.context.properties.ConfigurationPropertiesSource",
|
||||
"org.springframework.context.annotation.Configuration",
|
||||
"org.springframework.boot.actuate.endpoint.annotation.Endpoint",
|
||||
"org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint",
|
||||
@@ -592,4 +627,388 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
|
||||
assertThat(metadata.getIgnored()).containsExactly(ItemIgnore.forProperty("ignored.prop3"));
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SourceTests {
|
||||
|
||||
@Test
|
||||
void javaBeansSourceIsMergedWithNestedConfigurationProperty() {
|
||||
ConfigurationMetadata metadata = compile(SimpleSourceAnnotated.class, SimpleSource.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example.nested", SimpleSource.class))
|
||||
.has(Metadata.withProperty("example.nested.name", String.class).withDescription("Name description."))
|
||||
.has(Metadata.withProperty("example.nested.description", String.class)
|
||||
.withDescription("Description description.")
|
||||
.withDefaultValue("Hello World"))
|
||||
.has(Metadata.withProperty("example.nested.type", String.class)
|
||||
.withDescription("A property with a fixed set of values.")
|
||||
.withDefaultValue("single"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lombokSourceIsMergedWithNestedConfigurationProperty() {
|
||||
ConfigurationMetadata metadata = compile(LombokSourceAnnotated.class, LombokSource.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example.nested", LombokSource.class))
|
||||
.has(Metadata.withProperty("example.nested.name", String.class).withDescription("Name description."))
|
||||
.has(Metadata.withProperty("example.nested.description", String.class)
|
||||
.withDescription("Description description.")
|
||||
.withDefaultValue("Hello World"))
|
||||
.has(Metadata.withProperty("example.nested.type", String.class)
|
||||
.withDescription("A property with a fixed set of values.")
|
||||
.withDefaultValue("single"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void immutableSourceIsMergedWithNestedConfigurationProperty() {
|
||||
ConfigurationMetadata metadata = compile(ImmutableSourceAnnotated.class, ImmutableSource.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example.nested", ImmutableSource.class))
|
||||
.has(Metadata.withProperty("example.nested.name", String.class).withDescription("Name description."))
|
||||
.has(Metadata.withProperty("example.nested.description", String.class)
|
||||
.withDescription("Description description.")
|
||||
.withDefaultValue("Hello World"))
|
||||
.has(Metadata.withProperty("example.nested.type", String.class)
|
||||
.withDescription("A property with a fixed set of values.")
|
||||
.withDefaultValue("single"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordSourceIsMergedWithNestedConfigurationProperty() {
|
||||
ConfigurationMetadata metadata = compile(RecordSourceAnnotated.class, RecordSource.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example.nested", RecordSource.class))
|
||||
.has(Metadata.withProperty("example.nested.name", String.class).withDescription("Name description."))
|
||||
.has(Metadata.withProperty("example.nested.description", String.class)
|
||||
.withDescription("Description description.")
|
||||
.withDefaultValue("Hello World"))
|
||||
.has(Metadata.withProperty("example.nested.type", String.class)
|
||||
.withDescription("A property with a fixed set of values.")
|
||||
.withDefaultValue("single"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sourceIsMergedWithConfigurationProperties() {
|
||||
ConfigurationMetadata metadata = compile(ParentWithHintProperties.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example", ParentWithHintProperties.class))
|
||||
.has(Metadata.withProperty("example.name", String.class).withDescription("Name description."))
|
||||
.has(Metadata.withProperty("example.description", String.class)
|
||||
.withDescription("Description description.")
|
||||
.withDefaultValue("Hello World"))
|
||||
.has(Metadata.withProperty("example.type", String.class)
|
||||
.withDescription("A property with a fixed set of values.")
|
||||
.withDefaultValue("single"))
|
||||
.has(Metadata.withProperty("example.enabled", Boolean.class)
|
||||
.withDescription("Whether this is enabled.")
|
||||
.withDefaultValue(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sourceHintIsMergedWithNestedConfigurationProperty() {
|
||||
ConfigurationMetadata metadata = compile(SimpleSourceAnnotated.class);
|
||||
assertThat(metadata).has(Metadata.withHint("example.nested.type")
|
||||
.withValue(0, "auto", "Detect the type automatically.")
|
||||
.withValue(1, "single", "Single type.")
|
||||
.withValue(2, "multi", "Multi type."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sourceHintIsMergedWithConfigurationProperties() {
|
||||
ConfigurationMetadata metadata = compile(ParentWithHintProperties.class);
|
||||
assertThat(metadata).has(Metadata.withHint("example.type")
|
||||
.withValue(0, "auto", "Detect the type automatically.")
|
||||
.withValue(1, "single", "Single type.")
|
||||
.withValue(2, "multi", "Multi type."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sourceWithNonCanonicalMetadataIsDiscovered() {
|
||||
ConfigurationMetadata metadata = compile(ConventionSourceAnnotated.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example.nested", ConventionSource.class))
|
||||
.has(Metadata.withProperty("example.nested.first-name", String.class).withDescription("Camel case."))
|
||||
.has(Metadata.withProperty("example.nested.last-name", String.class)
|
||||
.withDescription("Canonical format."));
|
||||
assertThat(metadata.getItems()).hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sourceFromParentClasIsDiscoveredForConcreteSource() {
|
||||
ConfigurationMetadata metadata = compile(ConcreteSourceAnnotated.class, ConcreteSource.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example", ConcreteSourceAnnotated.class))
|
||||
.has(Metadata.withGroup("example.nested", ConcreteSource.class))
|
||||
.has(Metadata.withProperty("example.nested.enabled", Boolean.class)
|
||||
.withDescription("Whether the feature is enabled."))
|
||||
.has(Metadata.withProperty("example.nested.username", String.class)
|
||||
.withDescription("User name.")
|
||||
.withDefaultValue("user"))
|
||||
.has(Metadata.withProperty("example.nested.password", String.class).withDescription("Password."));
|
||||
assertThat(metadata.getItems()).hasSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sourceFromParentClasIsDiscoveredForConfigurationProperties() {
|
||||
ConfigurationMetadata metadata = compile(ConcreteProperties.class);
|
||||
assertThat(metadata).has(Metadata.withGroup("example", ConcreteProperties.class))
|
||||
.has(Metadata.withProperty("example.enabled", Boolean.class)
|
||||
.withDescription("Whether the feature is enabled."))
|
||||
.has(Metadata.withProperty("example.username", String.class)
|
||||
.withDescription("User name.")
|
||||
.withDefaultValue("user"))
|
||||
.has(Metadata.withProperty("example.password", String.class).withDescription("Password."));
|
||||
assertThat(metadata.getItems()).hasSize(4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SourceGenerationTests {
|
||||
|
||||
@Test
|
||||
void simplePropertiesSource() {
|
||||
compile(withTestClasses(SimplePropertiesSource.class), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(SimplePropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Description of this simple property."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."));
|
||||
assertThat(metadata.getItems()).hasSize(2);
|
||||
assertThat(metadata.getHints()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void simplePropertiesSourceWithAdditionalMetadataIsMerged() {
|
||||
String additionalMetadata = """
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "custom",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "Custom property description."
|
||||
}
|
||||
]
|
||||
}""";
|
||||
compile(withTestClasses(SimplePropertiesSource.class)
|
||||
.andThen(withAdditionalMetadata(SimplePropertiesSource.class, additionalMetadata)), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(SimplePropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Description of this simple property."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."))
|
||||
.has(Metadata.withProperty("custom")
|
||||
.ofType(Integer.class)
|
||||
.withDescription("Custom property description."));
|
||||
assertThat(metadata.getItems()).hasSize(3);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void simplePropertiesSourceWithAdditionalMetadataHintIsMerged() {
|
||||
String additionalMetadata = """
|
||||
{
|
||||
"hints": [
|
||||
{
|
||||
"name": "name",
|
||||
"values": [
|
||||
{ "value": "boot", "description": "Spring Boot." },
|
||||
{ "value": "framework", "description": "Spring Framework." }
|
||||
]
|
||||
}
|
||||
]
|
||||
}""";
|
||||
compile(withTestClasses(SimplePropertiesSource.class)
|
||||
.andThen(withAdditionalMetadata(SimplePropertiesSource.class, additionalMetadata)), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(SimplePropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Description of this simple property."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."))
|
||||
.has(Metadata.withHint("name")
|
||||
.withValue(0, "boot", "Spring Boot.")
|
||||
.withValue(1, "framework", "Spring Framework."));
|
||||
assertThat(metadata.getItems()).hasSize(2);
|
||||
assertThat(metadata.getHints()).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void simplePropertiesSourceWithAdditionalMetadataCanBeOverridden() {
|
||||
String additionalMetadata = """
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Custom description."
|
||||
}
|
||||
]
|
||||
}""";
|
||||
compile(withTestClasses(SimplePropertiesSource.class)
|
||||
.andThen(withAdditionalMetadata(SimplePropertiesSource.class, additionalMetadata)), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(SimplePropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Custom description."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."));
|
||||
assertThat(metadata.getItems()).hasSize(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void lombokPropertiesSource() {
|
||||
compile(withTestClasses(LombokPropertiesSource.class), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(LombokPropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Description of this simple property."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."));
|
||||
assertThat(metadata.getItems()).hasSize(2);
|
||||
assertThat(metadata.getHints()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void immutablePropertiesSource() {
|
||||
compile(withTestClasses(ImmutablePropertiesSource.class), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(ImmutablePropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Description of this simple property."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."));
|
||||
assertThat(metadata.getItems()).hasSize(2);
|
||||
assertThat(metadata.getHints()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordPropertiesSource() {
|
||||
compile(withTestClasses(RecordPropertiesSources.class), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(RecordPropertiesSources.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Description of this simple property."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."));
|
||||
assertThat(metadata.getItems()).hasSize(2);
|
||||
assertThat(metadata.getHints()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void abstractPropertiesSource() {
|
||||
compile(withTestClasses(AbstractPropertiesSource.class), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(AbstractPropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name")
|
||||
.ofType(String.class)
|
||||
.withDefaultValue("boot")
|
||||
.withDescription("Description of this simple property."))
|
||||
.has(Metadata.withProperty("enabled")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(false)
|
||||
.withDescription("Whether it is enabled."));
|
||||
assertThat(metadata.getItems()).hasSize(2);
|
||||
assertThat(metadata.getHints()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonRootConfigurationPropertiesSources() {
|
||||
compile(withTestClasses(ConfigurationPropertySourcesContainer.class), (compiled) -> {
|
||||
assertThat(CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(ConfigurationPropertySourcesContainer.class)))
|
||||
.isNull();
|
||||
ConfigurationMetadata firstMetadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(First.class));
|
||||
assertThat(firstMetadata).isNotNull()
|
||||
.has(Metadata.withProperty("name").ofType(String.class).withDescription("A name."));
|
||||
assertThat(firstMetadata.getItems()).hasSize(1);
|
||||
assertThat(firstMetadata.getHints()).isEmpty();
|
||||
ConfigurationMetadata secondMetadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(Second.class));
|
||||
assertThat(secondMetadata).isNotNull()
|
||||
.has(Metadata.withProperty("visible")
|
||||
.ofType(Boolean.class)
|
||||
.withDefaultValue(true)
|
||||
.withDescription("Whether this is visible."));
|
||||
assertThat(secondMetadata.getItems()).hasSize(1);
|
||||
assertThat(secondMetadata.getHints()).isEmpty();
|
||||
assertThat(CompiledMetadataReader.getMetadata(compiled, getSourceMetadataLocation(Third.class)))
|
||||
.isNull();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedPropertiesSource() {
|
||||
compile(withTestClasses(NestedPropertiesSource.class), (compiled) -> {
|
||||
ConfigurationMetadata metadata = CompiledMetadataReader.getMetadata(compiled,
|
||||
getSourceMetadataLocation(NestedPropertiesSource.class));
|
||||
assertThat(metadata).isNotNull()
|
||||
.has(Metadata.withProperty("name").ofType(String.class).withDescription("A name."))
|
||||
.has(Metadata.withGroup("nested").ofType(NestedPropertiesSource.Nested.class))
|
||||
.has(Metadata.withProperty("nested.name").ofType(String.class).withDescription("Another name."));
|
||||
assertThat(metadata.getItems()).hasSize(3);
|
||||
});
|
||||
}
|
||||
|
||||
private String getSourceMetadataLocation(Class<?> type) {
|
||||
return "META-INF/spring/configuration-metadata/%s.json".formatted(type.getName());
|
||||
}
|
||||
|
||||
private void compile(Function<TestCompiler, TestCompiler> configuration, Consumer<Compiled> compiled) {
|
||||
TestCompiler testCompiler = TestCompiler.forSystem();
|
||||
configuration.apply(testCompiler)
|
||||
.withProcessors(new TestConfigurationMetadataAnnotationProcessor())
|
||||
.compile(compiled);
|
||||
}
|
||||
|
||||
private Function<TestCompiler, TestCompiler> withTestClasses(Class<?>... testClasses) {
|
||||
return (compiler) -> compiler
|
||||
.withSources(Arrays.stream(testClasses).map(SourceFile::forTestClass).toList());
|
||||
}
|
||||
|
||||
private Function<TestCompiler, TestCompiler> withAdditionalMetadata(Class<?> type, String content) {
|
||||
String location = "META-INF/spring/configuration-metadata/additional/%s.json".formatted(type.getName());
|
||||
return (compiler) -> compiler.withResources(ResourceFile.of(location, content));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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 java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint.ValueHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
|
||||
import org.springframework.boot.configurationprocessor.metadata.Metadata;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link MetadataCollector}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class MetadataCollectorTests {
|
||||
|
||||
private static final Predicate<ItemMetadata> NO_MERGE = (metadata) -> false;
|
||||
|
||||
private static final ConfigurationMetadata SINGLE_ITEM_METADATA = readMetadata("""
|
||||
{
|
||||
"properties": [
|
||||
{ "name": "name", "type": "java.lang.String" }
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
@Test
|
||||
void addSingleItemMetadata() {
|
||||
MetadataCollector collector = createSimpleCollector();
|
||||
collector.add(SINGLE_ITEM_METADATA.getItems().get(0));
|
||||
assertThat(collector.getMetadata()).has(Metadata.withProperty("name", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addIfAbsentAddsPropertyIfItDoesNotExist() {
|
||||
MetadataCollector collector = createSimpleCollector();
|
||||
collector.addIfAbsent(SINGLE_ITEM_METADATA.getItems().get(0));
|
||||
ConfigurationMetadata metadata = collector.getMetadata();
|
||||
assertThat(metadata).has(Metadata.withProperty("name", String.class));
|
||||
assertThat(metadata.getItems()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addIfAbsentIgnoresExistingProperty() {
|
||||
MetadataCollector collector = createSimpleCollector();
|
||||
collector.addIfAbsent(SINGLE_ITEM_METADATA.getItems().get(0));
|
||||
collector.addIfAbsent(SINGLE_ITEM_METADATA.getItems().get(0));
|
||||
collector.addIfAbsent(SINGLE_ITEM_METADATA.getItems().get(0));
|
||||
ConfigurationMetadata metadata = collector.getMetadata();
|
||||
assertThat(metadata).has(Metadata.withProperty("name", String.class));
|
||||
assertThat(metadata.getItems()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void addNewMetadataDoesNotInvokeConflictResolution() {
|
||||
MetadataCollector collector = createSimpleCollector();
|
||||
Consumer<ItemMetadata> conflictResolution = mock(Consumer.class);
|
||||
collector.add(SINGLE_ITEM_METADATA.getItems().get(0), conflictResolution);
|
||||
then(conflictResolution).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void addMetadataWithExistingInstanceInvokesConflictResolution() {
|
||||
MetadataCollector collector = createSimpleCollector();
|
||||
ItemMetadata metadata = SINGLE_ITEM_METADATA.getItems().get(0);
|
||||
collector.add(metadata);
|
||||
Consumer<ItemMetadata> conflictResolution = mock(Consumer.class);
|
||||
collector.add(metadata, conflictResolution);
|
||||
then(conflictResolution).should().accept(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSingleItemHint() {
|
||||
MetadataCollector collector = createSimpleCollector();
|
||||
collector.add(SINGLE_ITEM_METADATA.getItems().get(0));
|
||||
ValueHint firstValueHint = new ValueHint("one", "First.");
|
||||
ValueHint secondValueHint = new ValueHint("two", "Second.");
|
||||
ItemHint itemHint = new ItemHint("name", List.of(firstValueHint, secondValueHint), Collections.emptyList());
|
||||
collector.add(itemHint);
|
||||
assertThat(collector.getMetadata())
|
||||
.has(Metadata.withHint("name").withValue(0, "one", "First.").withValue(1, "two", "Second."));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void getMetadataDoesNotInvokeMergeFunctionIfPreviousMetadataIsNull() {
|
||||
Predicate<ItemMetadata> mergedRequired = mock(Predicate.class);
|
||||
MetadataCollector collector = new MetadataCollector(mergedRequired, null);
|
||||
collector.add(SINGLE_ITEM_METADATA.getItems().get(0));
|
||||
collector.getMetadata();
|
||||
then(mergedRequired).shouldHaveNoInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void getMetadataAddPreviousItemIfMergeFunctionReturnsTrue() {
|
||||
Predicate<ItemMetadata> mergedRequired = mock(Predicate.class);
|
||||
ItemMetadata itemMetadata = SINGLE_ITEM_METADATA.getItems().get(0);
|
||||
given(mergedRequired.test(itemMetadata)).willReturn(true);
|
||||
MetadataCollector collector = new MetadataCollector(mergedRequired, SINGLE_ITEM_METADATA);
|
||||
assertThat(collector.getMetadata()).has(Metadata.withProperty("name", String.class));
|
||||
then(mergedRequired).should().test(itemMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void getMetadataDoesNotAddPreviousItemIfMergeFunctionReturnsFalse() {
|
||||
Predicate<ItemMetadata> mergedRequired = mock(Predicate.class);
|
||||
ItemMetadata itemMetadata = SINGLE_ITEM_METADATA.getItems().get(0);
|
||||
given(mergedRequired.test(itemMetadata)).willReturn(false);
|
||||
MetadataCollector collector = new MetadataCollector(mergedRequired, SINGLE_ITEM_METADATA);
|
||||
assertThat(collector.getMetadata().getItems()).isEmpty();
|
||||
then(mergedRequired).should().test(itemMetadata);
|
||||
}
|
||||
|
||||
private MetadataCollector createSimpleCollector() {
|
||||
return new MetadataCollector(NO_MERGE, null);
|
||||
}
|
||||
|
||||
private static ConfigurationMetadata readMetadata(String json) {
|
||||
try {
|
||||
JsonMarshaller marshaller = new JsonMarshaller();
|
||||
return marshaller.read(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Invalid JSON: " + json, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,7 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron
|
||||
TestConfigurationMetadataAnnotationProcessor.WEB_ENDPOINT_ANNOTATION));
|
||||
return new MetadataGenerationEnvironment(environment,
|
||||
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_SOURCE_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.NESTED_CONFIGURATION_PROPERTY_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.CONSTRUCTOR_BINDING_ANNOTATION,
|
||||
|
||||
@@ -41,7 +41,7 @@ class MetadataStoreTests {
|
||||
|
||||
private final ProcessingEnvironment environment = mock(ProcessingEnvironment.class);
|
||||
|
||||
private final MetadataStore metadataStore = new MetadataStore(this.environment);
|
||||
private final MetadataStore metadataStore = new MetadataStore(this.environment, mock(TypeUtils.class));
|
||||
|
||||
@Test
|
||||
void additionalMetadataIsLocatedInMavenBuild() throws IOException {
|
||||
@@ -52,7 +52,8 @@ class MetadataStoreTests {
|
||||
File additionalMetadata = new File(metaInf, "additional-spring-configuration-metadata.json");
|
||||
additionalMetadata.createNewFile();
|
||||
assertThat(this.metadataStore.locateAdditionalMetadataFile(
|
||||
new File(classesLocation, "META-INF/additional-spring-configuration-metadata.json")))
|
||||
new File(classesLocation, "META-INF/additional-spring-configuration-metadata.json"),
|
||||
"META-INF/additional-spring-configuration-metadata.json"))
|
||||
.isEqualTo(additionalMetadata);
|
||||
}
|
||||
|
||||
@@ -66,7 +67,8 @@ class MetadataStoreTests {
|
||||
File additionalMetadata = new File(metaInf, "additional-spring-configuration-metadata.json");
|
||||
additionalMetadata.createNewFile();
|
||||
assertThat(this.metadataStore.locateAdditionalMetadataFile(
|
||||
new File(classesLocation, "META-INF/additional-spring-configuration-metadata.json")))
|
||||
new File(classesLocation, "META-INF/additional-spring-configuration-metadata.json"),
|
||||
"META-INF/additional-spring-configuration-metadata.json"))
|
||||
.isEqualTo(additionalMetadata);
|
||||
}
|
||||
|
||||
@@ -80,7 +82,8 @@ class MetadataStoreTests {
|
||||
File additionalMetadata = new File(metaInf, "additional-spring-configuration-metadata.json");
|
||||
additionalMetadata.createNewFile();
|
||||
assertThat(this.metadataStore.locateAdditionalMetadataFile(
|
||||
new File(classesLocation, "META-INF/additional-spring-configuration-metadata.json")))
|
||||
new File(classesLocation, "META-INF/additional-spring-configuration-metadata.json"),
|
||||
"META-INF/additional-spring-configuration-metadata.json"))
|
||||
.isEqualTo(additionalMetadata);
|
||||
}
|
||||
|
||||
@@ -95,7 +98,9 @@ class MetadataStoreTests {
|
||||
given(this.environment.getOptions()).willReturn(
|
||||
Collections.singletonMap(ConfigurationMetadataAnnotationProcessor.ADDITIONAL_METADATA_LOCATIONS_OPTION,
|
||||
location.getAbsolutePath()));
|
||||
assertThat(this.metadataStore.locateAdditionalMetadataFile(new File(app, "foo"))).isEqualTo(additionalMetadata);
|
||||
assertThat(this.metadataStore.locateAdditionalMetadataFile(new File(app, "foo"),
|
||||
"META-INF/additional-spring-configuration-metadata.json"))
|
||||
.isEqualTo(additionalMetadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.metadata;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint.ValueHint;
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemHint.ValueProvider;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ItemHint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ItemHintTests {
|
||||
|
||||
@Test
|
||||
void prefixIsAppliedWithValueHint() {
|
||||
ValueHint firstValueHint = new ValueHint("one", "First.");
|
||||
ValueHint secondValueHint = new ValueHint("two", "Second.");
|
||||
ItemHint itemHint = new ItemHint("name", List.of(firstValueHint, secondValueHint), Collections.emptyList());
|
||||
ItemHint prefixedItemHint = itemHint.applyPrefix("example");
|
||||
assertThat(itemHint).isNotSameAs(prefixedItemHint);
|
||||
assertThat(prefixedItemHint.getName()).isEqualTo("example.name");
|
||||
assertThat(prefixedItemHint.getValues()).containsExactly(firstValueHint, secondValueHint);
|
||||
assertThat(prefixedItemHint.getProviders()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixIsAppliedWithValueProvider() {
|
||||
ValueProvider firstValueProvider = new ValueProvider("class-reference", Map.of("target", String.class));
|
||||
ValueProvider secondValueProvider = new ValueProvider("any", Collections.emptyMap());
|
||||
ItemHint itemHint = new ItemHint("name", Collections.emptyList(),
|
||||
List.of(firstValueProvider, secondValueProvider));
|
||||
ItemHint prefixedItemHint = itemHint.applyPrefix("example");
|
||||
assertThat(itemHint).isNotSameAs(prefixedItemHint);
|
||||
assertThat(prefixedItemHint.getName()).isEqualTo("example.name");
|
||||
assertThat(prefixedItemHint.getValues()).isEmpty();
|
||||
assertThat(prefixedItemHint.getProviders()).containsExactly(firstValueProvider, secondValueProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixIsAppliedWithConvention() {
|
||||
ItemHint itemHint = new ItemHint("name", Collections.emptyList(), Collections.emptyList());
|
||||
ItemHint prefixedItemHint = itemHint.applyPrefix("example.nestedType");
|
||||
assertThat(prefixedItemHint.getName()).isEqualTo("example.nested-type.name");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.springframework.core.test.tools.TestCompiler;
|
||||
* Read the contents of metadata generated from the {@link TestCompiler}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public final class CompiledMetadataReader {
|
||||
|
||||
@@ -36,7 +37,11 @@ public final class CompiledMetadataReader {
|
||||
}
|
||||
|
||||
public static ConfigurationMetadata getMetadata(Compiled compiled) {
|
||||
InputStream inputStream = compiled.getClassLoader().getResourceAsStream(METADATA_FILE);
|
||||
return getMetadata(compiled, METADATA_FILE);
|
||||
}
|
||||
|
||||
public static ConfigurationMetadata getMetadata(Compiled compiled, String location) {
|
||||
InputStream inputStream = compiled.getClassLoader().getResourceAsStream(location);
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
return new JsonMarshaller().read(inputStream);
|
||||
@@ -46,7 +51,7 @@ public final class CompiledMetadataReader {
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException("Failed to read metadata", ex);
|
||||
throw new RuntimeException("Failed to read metadata fom '%s'".formatted(location), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.boot.configurationprocessor.ConfigurationMetadataAnno
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SupportedAnnotationTypes({ TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_SOURCE_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.CONTROLLER_ENDPOINT_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.JMX_ENDPOINT_ANNOTATION,
|
||||
@@ -48,6 +49,8 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
|
||||
|
||||
public static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationProperties";
|
||||
|
||||
public static final String CONFIGURATION_PROPERTIES_SOURCE_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationPropertiesSource";
|
||||
|
||||
public static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.NestedConfigurationProperty";
|
||||
|
||||
public static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.DeprecatedConfigurationProperty";
|
||||
@@ -86,6 +89,11 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
|
||||
return CONFIGURATION_PROPERTIES_ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String configurationPropertiesSourceAnnotation() {
|
||||
return CONFIGURATION_PROPERTIES_SOURCE_ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String nestedConfigurationPropertyAnnotation() {
|
||||
return NESTED_CONFIGURATION_PROPERTY_ANNOTATION;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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 @ConfigurationProperties} for testing (removes the
|
||||
* need for a dependency on the real annotation).
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ConfigurationPropertiesSource {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
/**
|
||||
* Test properties contributed by a parent source.
|
||||
*/
|
||||
public abstract class BaseSource {
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
private String username;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
protected abstract String getPassword();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("example")
|
||||
public class ConcreteProperties extends BaseSource {
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*/
|
||||
private String password;
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
public class ConcreteSource extends BaseSource {
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*/
|
||||
private String password;
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
|
||||
|
||||
@ConfigurationProperties("example")
|
||||
public class ConcreteSourceAnnotated {
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private final ConcreteSource nested = new ConcreteSource();
|
||||
|
||||
public ConcreteSource getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
/**
|
||||
* Test various convention-based lookups.
|
||||
*/
|
||||
public class ConventionSource {
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
|
||||
public String getFirstName() {
|
||||
return this.firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return this.lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
|
||||
|
||||
@ConfigurationProperties(prefix = "example")
|
||||
public class ConventionSourceAnnotated {
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private final ConventionSource nested = new ConventionSource();
|
||||
|
||||
public ConventionSource getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
/**
|
||||
* Immutable type with manual metadata. This illustrates the case where the type of a
|
||||
* property is defined in a separate class and source-based metadata cannot be discovered.
|
||||
*/
|
||||
public class ImmutableSource {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final String type;
|
||||
|
||||
public ImmutableSource(String name, String description, String type) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
|
||||
|
||||
@ConfigurationProperties("example")
|
||||
public class ImmutableSourceAnnotated {
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private final ImmutableSource nested;
|
||||
|
||||
public ImmutableSourceAnnotated(ImmutableSource nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Type using lombok with manual metadata. This illustrates the case where the type of a
|
||||
* property is defined in a separate class and source-based metadata cannot be discovered.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class LombokSource {
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private String type;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
|
||||
|
||||
@Getter
|
||||
@ConfigurationProperties("example")
|
||||
public class LombokSourceAnnotated {
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private final LombokSource nested = new LombokSource();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("example")
|
||||
public class ParentWithHintProperties extends SimpleSource {
|
||||
|
||||
/**
|
||||
* Whether this is enabled.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
/**
|
||||
* Record type with manual metadata. This illustrates the case where the type of a
|
||||
* property is defined in a separate class and source-based metadata cannot be discovered.
|
||||
*/
|
||||
public record RecordSource(String name, String description, String type) {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
|
||||
|
||||
@ConfigurationProperties("example")
|
||||
public record RecordSourceAnnotated(@NestedConfigurationProperty RecordSource nested) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
/**
|
||||
* Type with manual metadata. This illustrates the case where the type of a property is
|
||||
* defined in a separate class and source-based metadata cannot be discovered.
|
||||
*/
|
||||
public class SimpleSource {
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private String type;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
|
||||
|
||||
@ConfigurationProperties("example")
|
||||
public class SimpleSourceAnnotated {
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private final SimpleSource nested = new SimpleSource();
|
||||
|
||||
public SimpleSource getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source.generation;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationPropertiesSource;
|
||||
|
||||
@ConfigurationPropertiesSource
|
||||
public abstract class AbstractPropertiesSource {
|
||||
|
||||
/**
|
||||
* Description of this simple property.
|
||||
*/
|
||||
private String name = "boot";
|
||||
|
||||
/**
|
||||
* Whether it is enabled.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source.generation;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationPropertiesSource;
|
||||
|
||||
public class ConfigurationPropertySourcesContainer {
|
||||
|
||||
@ConfigurationPropertiesSource
|
||||
public static class First {
|
||||
|
||||
/**
|
||||
* A name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationPropertiesSource
|
||||
public static class Second {
|
||||
|
||||
/**
|
||||
* Whether this is visible.
|
||||
*/
|
||||
private boolean visible = true;
|
||||
|
||||
public boolean isVisible() {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Third {
|
||||
|
||||
private String shouldBeIgnored;
|
||||
|
||||
public String getShouldBeIgnored() {
|
||||
return this.shouldBeIgnored;
|
||||
}
|
||||
|
||||
public void setShouldBeIgnored(String shouldBeIgnored) {
|
||||
this.shouldBeIgnored = shouldBeIgnored;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source.generation;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationPropertiesSource;
|
||||
import org.springframework.boot.configurationsample.DefaultValue;
|
||||
|
||||
@ConfigurationPropertiesSource
|
||||
public class ImmutablePropertiesSource {
|
||||
|
||||
/**
|
||||
* Description of this simple property.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Whether it is enabled.
|
||||
*/
|
||||
private final boolean enabled;
|
||||
|
||||
public ImmutablePropertiesSource(@DefaultValue("boot") String name, boolean enabled) {
|
||||
this.name = name;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source.generation;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationPropertiesSource;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationPropertiesSource
|
||||
public class LombokPropertiesSource {
|
||||
|
||||
/**
|
||||
* Description of this simple property.
|
||||
*/
|
||||
private String name = "boot";
|
||||
|
||||
/**
|
||||
* Whether it is enabled.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source.generation;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationPropertiesSource;
|
||||
|
||||
@ConfigurationPropertiesSource
|
||||
public class NestedPropertiesSource {
|
||||
|
||||
/**
|
||||
* A name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
private final Nested nested = new Nested();
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Nested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
public static class Nested {
|
||||
|
||||
/**
|
||||
* Another name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source.generation;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationPropertiesSource;
|
||||
import org.springframework.boot.configurationsample.DefaultValue;
|
||||
|
||||
/**
|
||||
* Sample record properties source.
|
||||
*
|
||||
* @param name Description of this simple property.
|
||||
* @param enabled Whether it is enabled.
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ConfigurationPropertiesSource
|
||||
public record RecordPropertiesSources(@DefaultValue("boot") String name, boolean enabled) {
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.source.generation;
|
||||
|
||||
import org.springframework.boot.configurationsample.ConfigurationPropertiesSource;
|
||||
|
||||
@ConfigurationPropertiesSource
|
||||
public class SimplePropertiesSource {
|
||||
|
||||
/**
|
||||
* Description of this simple property.
|
||||
*/
|
||||
private String name = "boot";
|
||||
|
||||
/**
|
||||
* Whether it is enabled.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether the feature is enabled."
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"type": "java.lang.String",
|
||||
"description": "User name.",
|
||||
"defaultValue": "user"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "firstName",
|
||||
"type": "java.lang.String",
|
||||
"description": "Camel case."
|
||||
},
|
||||
{
|
||||
"name": "last-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Canonical format."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Name description."
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "java.lang.String",
|
||||
"description": "Description description.",
|
||||
"defaultValue": "Hello World"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"type": "java.lang.String",
|
||||
"description": "A property with a fixed set of values.",
|
||||
"defaultValue": "single"
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
{
|
||||
"name": "type",
|
||||
"values": [
|
||||
{
|
||||
"value": "auto",
|
||||
"description": "Detect the type automatically."
|
||||
},
|
||||
{
|
||||
"value": "single",
|
||||
"description": "Single type."
|
||||
},
|
||||
{
|
||||
"value": "multi",
|
||||
"description": "Multi type."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Name description."
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "java.lang.String",
|
||||
"description": "Description description.",
|
||||
"defaultValue": "Hello World"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"type": "java.lang.String",
|
||||
"description": "A property with a fixed set of values.",
|
||||
"defaultValue": "single"
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
{
|
||||
"name": "type",
|
||||
"values": [
|
||||
{
|
||||
"value": "auto",
|
||||
"description": "Detect the type automatically."
|
||||
},
|
||||
{
|
||||
"value": "single",
|
||||
"description": "Single type."
|
||||
},
|
||||
{
|
||||
"value": "multi",
|
||||
"description": "Multi type."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Name description."
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "java.lang.String",
|
||||
"description": "Description description.",
|
||||
"defaultValue": "Hello World"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"type": "java.lang.String",
|
||||
"description": "A property with a fixed set of values.",
|
||||
"defaultValue": "single"
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
{
|
||||
"name": "type",
|
||||
"values": [
|
||||
{
|
||||
"value": "auto",
|
||||
"description": "Detect the type automatically."
|
||||
},
|
||||
{
|
||||
"value": "single",
|
||||
"description": "Single type."
|
||||
},
|
||||
{
|
||||
"value": "multi",
|
||||
"description": "Multi type."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Name description."
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "java.lang.String",
|
||||
"description": "Description description.",
|
||||
"defaultValue": "Hello World"
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"type": "java.lang.String",
|
||||
"description": "A property with a fixed set of values.",
|
||||
"defaultValue": "single"
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
{
|
||||
"name": "type",
|
||||
"values": [
|
||||
{
|
||||
"value": "auto",
|
||||
"description": "Detect the type automatically."
|
||||
},
|
||||
{
|
||||
"value": "single",
|
||||
"description": "Single type."
|
||||
},
|
||||
{
|
||||
"value": "multi",
|
||||
"description": "Multi type."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user