From 31b0264d94c2f5690ca18e8d2be3a8e4f75d0d15 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 10 Jun 2022 14:18:49 -0700 Subject: [PATCH] Ensure conversion service actually converts to the correct type Update `BindConverter` with a guard to ensure that the resulting object is the correct type. Fixes gh-28592 --- .../properties/bind/BindConverter.java | 7 ++- .../ConfigurationPropertiesTests.java | 46 +++++++++++++++++++ .../WithPublicObjectToObjectMethod.java | 43 +++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/WithPublicObjectToObjectMethod.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java index a38335401f..0e477296d9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -106,7 +106,10 @@ final class BindConverter { for (ConversionService delegate : this.delegates) { try { if (delegate.canConvert(sourceType, targetType)) { - return delegate.convert(source, sourceType, targetType); + Object converted = delegate.convert(source, sourceType, targetType); + if (targetType.getType().isInstance(converted)) { + return converted; + } } } catch (ConversionException ex) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 79cd1b6777..eda312947a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -57,6 +57,7 @@ import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.convert.DataSizeUnit; import org.springframework.boot.convert.DurationFormat; import org.springframework.boot.convert.DurationStyle; @@ -1081,6 +1082,15 @@ class ConfigurationPropertiesTests { assertThat(keys.stream().map(ConfigurationPropertyName::toString)).contains("name", "nested.name"); } + @Test // gh-28592 + void loadWhenBindingWithCustomConverterAndObjectToObjectMethod() { + this.context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); + load(WithCustomConverterAndObjectToObjectMethodConfiguration.class, "test.item=foo"); + WithCustomConverterAndObjectToObjectMethodProperties bean = this.context + .getBean(WithCustomConverterAndObjectToObjectMethodProperties.class); + assertThat(bean.getItem().getValue()).isEqualTo("foo"); + } + private AnnotationConfigApplicationContext load(Class configuration, String... inlinedProperties) { return load(new Class[] { configuration }, inlinedProperties); } @@ -2710,4 +2720,40 @@ class ConfigurationPropertiesTests { } + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(WithCustomConverterAndObjectToObjectMethodProperties.class) + static class WithCustomConverterAndObjectToObjectMethodConfiguration { + + @Bean + @ConfigurationPropertiesBinding + WithObjectToObjectMethodConverter withObjectToObjectMethodConverter() { + return new WithObjectToObjectMethodConverter(); + } + + } + + @ConfigurationProperties("test") + static class WithCustomConverterAndObjectToObjectMethodProperties { + + private WithPublicObjectToObjectMethod item; + + WithPublicObjectToObjectMethod getItem() { + return this.item; + } + + void setItem(WithPublicObjectToObjectMethod item) { + this.item = item; + } + + } + + static class WithObjectToObjectMethodConverter implements Converter { + + @Override + public WithPublicObjectToObjectMethod convert(String source) { + return new WithPublicObjectToObjectMethod(source); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/WithPublicObjectToObjectMethod.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/WithPublicObjectToObjectMethod.java new file mode 100644 index 0000000000..f3a5b167c3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/WithPublicObjectToObjectMethod.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties; + +import java.util.Optional; + +/** + * Data object with a pubic method picked up by the {@code ObjectToObjectConverter}. Used + * in {@link ConfigurationPropertiesTests}. + * + * @author Phillip Webb + */ +public class WithPublicObjectToObjectMethod { + + private final String value; + + WithPublicObjectToObjectMethod(String value) { + this.value = value; + } + + String getValue() { + return this.value; + } + + public static Optional from(String value) { + return Optional.of(new WithPublicObjectToObjectMethod(value)); + } + +} \ No newline at end of file