diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java index 8007323eca9..3636e2c1708 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -26,6 +26,7 @@ import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler; +import org.springframework.boot.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler; import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler; import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; @@ -103,7 +104,7 @@ class ConfigurationPropertiesBinder { private BindHandler getBindHandler(ConfigurationProperties annotation, List validators) { - BindHandler handler = BindHandler.DEFAULT; + BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler(); if (annotation.ignoreInvalidFields()) { handler = new IgnoreErrorsBindHandler(handler); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandler.java new file mode 100644 index 00000000000..962d41249ed --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties.bind.handler; + +import org.springframework.boot.context.properties.bind.AbstractBindHandler; +import org.springframework.boot.context.properties.bind.BindContext; +import org.springframework.boot.context.properties.bind.BindHandler; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.core.convert.ConverterNotFoundException; + +/** + * {@link BindHandler} that can be used to ignore top-level {@link ConverterNotFoundException}s. + * + * @author Madhura Bhave + * @since 2.0.0 + */ +public class IgnoreTopLevelConverterNotFoundBindHandler extends AbstractBindHandler { + + @Override + public Object onFailure(ConfigurationPropertyName name, Bindable target, + BindContext context, Exception error) throws Exception { + if (context.getDepth() == 0 && error instanceof ConverterNotFoundException) { + return null; + } + throw error; + } + +} + + 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 e48a3e447bc..5cd7ec1d03d 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 @@ -745,6 +745,11 @@ public class ConfigurationPropertiesTests { assertThat(bean.getFile()).isEqualTo(new File(".")); } + @Test + public void loadWhenTopLevelConverterNotFoundExceptionShouldNotFail() { + load(PersonProperties.class, "test=boot"); + } + private AnnotationConfigApplicationContext load(Class configuration, String... inlinedProperties) { return load(new Class[] { configuration }, inlinedProperties); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandlerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandlerTests.java new file mode 100644 index 00000000000..d08d38a9829 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandlerTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties.bind.handler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.context.properties.bind.BindException; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.source.ConfigurationPropertySource; +import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; +import org.springframework.core.convert.ConverterNotFoundException; + +import static org.hamcrest.Matchers.instanceOf; + +/** + * Tests for {@link IgnoreTopLevelConverterNotFoundBindHandler}. + * + * @author Madhura Bhave + */ +public class IgnoreTopLevelConverterNotFoundBindHandlerTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private List sources = new ArrayList<>(); + + private Binder binder; + + @Before + public void setup() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("example", "bar"); + this.sources.add(source); + this.binder = new Binder(this.sources); + } + + @After + public void tearDown() { + this.sources.clear(); + } + + @Test + public void bindWhenHandlerNotPresentShouldFail() { + this.thrown.expectCause(instanceOf(ConverterNotFoundException.class)); + this.binder.bind("example", Bindable.of(Example.class)); + } + + @Test + public void bindWhenTopLevelContextAndExceptionIgnorableShouldNotFail() { + this.binder.bind("example", Bindable.of(Example.class), new IgnoreTopLevelConverterNotFoundBindHandler()); + } + + @Test + public void bindWhenExceptionNotIgnorableShouldNotFail() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("example.foo", "1"); + this.sources.add(source); + this.thrown.expectCause(instanceOf(IllegalStateException.class)); + this.binder.bind("example", Bindable.of(Example.class), new IgnoreTopLevelConverterNotFoundBindHandler()); + } + + @Test + public void bindWhenExceptionInNestedContextShouldFail() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("example.map", "hello"); + this.sources.add(source); + this.thrown.expect(BindException.class); + this.thrown.expectCause(instanceOf(ConverterNotFoundException.class)); + this.binder.bind("example", Bindable.of(Example.class), new IgnoreTopLevelConverterNotFoundBindHandler()); + } + + public static class Example { + + private int foo; + + private Map map; + + public int getFoo() { + return this.foo; + } + + public void setFoo(int foo) { + throw new IllegalStateException(); + } + + public Map getMap() { + return this.map; + } + + public void setMap(Map map) { + this.map = map; + } + + } + +}