Fix binding of classpath*: to resource arrays and collections

Fixes gh-15835
This commit is contained in:
Andy Wilkinson 2023-10-16 11:27:01 +01:00
parent 339f75d309
commit 0e3a196af5
2 changed files with 71 additions and 6 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -42,6 +42,7 @@ import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
/** /**
@ -154,8 +155,8 @@ final class BindConverter {
private static class TypeConverterConversionService extends GenericConversionService { private static class TypeConverterConversionService extends GenericConversionService {
TypeConverterConversionService(Consumer<PropertyEditorRegistry> initializer) { TypeConverterConversionService(Consumer<PropertyEditorRegistry> initializer) {
addConverter(new TypeConverterConverter(initializer));
ApplicationConversionService.addDelimitedStringConverters(this); ApplicationConversionService.addDelimitedStringConverters(this);
addConverter(new TypeConverterConverter(initializer));
} }
@Override @Override
@ -196,16 +197,23 @@ final class BindConverter {
@Override @Override
public Set<ConvertiblePair> getConvertibleTypes() { public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Object.class)); return Set.of(new ConvertiblePair(String.class, Object.class),
new ConvertiblePair(String.class, Object[].class),
new ConvertiblePair(String.class, Collection.class));
} }
@Override @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
Class<?> type = targetType.getType(); Class<?> type = targetType.getType();
if (type == null || type == Object.class || Collection.class.isAssignableFrom(type) if (type == null || type == Object.class || Map.class.isAssignableFrom(type)) {
|| Map.class.isAssignableFrom(type)) {
return false; return false;
} }
if (Collection.class.isAssignableFrom(type)) {
TypeDescriptor elementType = targetType.getElementTypeDescriptor();
if (elementType == null || (!Resource.class.isAssignableFrom(elementType.getType()))) {
return false;
}
}
PropertyEditor editor = this.matchesOnlyTypeConverter.getDefaultEditor(type); PropertyEditor editor = this.matchesOnlyTypeConverter.getDefaultEditor(type);
if (editor == null) { if (editor == null) {
editor = this.matchesOnlyTypeConverter.findCustomEditor(type, null); editor = this.matchesOnlyTypeConverter.findCustomEditor(type, null);
@ -218,7 +226,7 @@ final class BindConverter {
@Override @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return createTypeConverter().convertIfNecessary(source, targetType.getType()); return createTypeConverter().convertIfNecessary(source, targetType.getType(), targetType);
} }
private SimpleTypeConverter createTypeConverter() { private SimpleTypeConverter createTypeConverter() {

View File

@ -23,6 +23,7 @@ import java.time.Duration;
import java.time.Period; import java.time.Period;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -1173,6 +1174,22 @@ class ConfigurationPropertiesTests {
assertThat(properties.getProp()).isEqualTo("alpha"); assertThat(properties.getProp()).isEqualTo("alpha");
} }
@Test
void loadWhenBindingClasspathPatternToResourceArrayShouldBindMultipleValues() {
load(ResourceArrayPropertiesConfiguration.class,
"test.resources=classpath*:org/springframework/boot/context/properties/*.class");
ResourceArrayProperties properties = this.context.getBean(ResourceArrayProperties.class);
assertThat(properties.getResources()).hasSizeGreaterThan(1);
}
@Test
void loadWhenBindingClasspathPatternToResourceCollectionShouldBindMultipleValues() {
load(ResourceCollectionPropertiesConfiguration.class,
"test.resources=classpath*:org/springframework/boot/context/properties/*.class");
ResourceCollectionProperties properties = this.context.getBean(ResourceCollectionProperties.class);
assertThat(properties.getResources()).hasSizeGreaterThan(1);
}
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) { private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
return load(new Class<?>[] { configuration }, inlinedProperties); return load(new Class<?>[] { configuration }, inlinedProperties);
} }
@ -3058,4 +3075,44 @@ class ConfigurationPropertiesTests {
} }
@EnableConfigurationProperties(ResourceArrayProperties.class)
static class ResourceArrayPropertiesConfiguration {
}
@ConfigurationProperties("test")
static class ResourceArrayProperties {
private Resource[] resources;
Resource[] getResources() {
return this.resources;
}
void setResources(Resource[] resources) {
this.resources = resources;
}
}
@EnableConfigurationProperties(ResourceCollectionProperties.class)
static class ResourceCollectionPropertiesConfiguration {
}
@ConfigurationProperties("test")
static class ResourceCollectionProperties {
private Collection<Resource> resources;
Collection<Resource> getResources() {
return this.resources;
}
void setResources(Collection<Resource> resources) {
this.resources = resources;
}
}
} }