From b0d9a8322e11529e3ea5b6997e3610e434b303e8 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 29 Sep 2015 15:03:50 -0700 Subject: [PATCH] Support `.` and `_` binder prefix joins Update RelaxedDataBinder so that both `.` and `_` are considered in getPropertyValuesForNamePrefix(...). With Spring Boot 1.2.5 binding environment variables of the form `FOO_BAR_BAZ` to `@ConfigurationProperties(prefix="foo-bar")` objects worked thanks to a happy accident. When `PropertySourcesPropertyValues` processed a non enumerable `PropertySource` it called the resolver with a property name `FOO_BAR.BAZ`. A `SystemEnvironmentPropertySource` will replace `.` with `_` and hence find a value. Commit 1abd0879 updated non enumerable processing such that the resolver was never called. Replicating the problem is quite involved as you need to ensure that you have both a SystemEnvironmentPropertySource and a non-enumerable property source (e.g. RandomPropertySource). A test has been added to PropertiesConfigurationFactoryTests which passes on 1.2.5, fails on 1.2.6 and passes again following this commit. Fixes gh-4045 --- .../boot/bind/RelaxedDataBinder.java | 21 ++++++++++++++----- .../PropertiesConfigurationFactoryTests.java | 19 +++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java index b4018a6aaef..612e8ee02f4 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java @@ -201,11 +201,15 @@ public class RelaxedDataBinder extends DataBinder { MutablePropertyValues rtn = new MutablePropertyValues(); for (PropertyValue value : propertyValues.getPropertyValues()) { String name = value.getName(); - for (String candidate : new RelaxedNames(this.namePrefix)) { - if (name.startsWith(candidate)) { - name = name.substring(candidate.length()); - if (!(this.ignoreNestedProperties && name.contains("."))) { - rtn.add(name, value.getValue()); + for (String prefix : new RelaxedNames(stripLastDot(this.namePrefix))) { + for (String separator : new String[] { ".", "_" }) { + String candidate = (StringUtils.hasLength(prefix) ? prefix + + separator : prefix); + if (name.startsWith(candidate)) { + name = name.substring(candidate.length()); + if (!(this.ignoreNestedProperties && name.contains("."))) { + rtn.add(name, value.getValue()); + } } } } @@ -213,6 +217,13 @@ public class RelaxedDataBinder extends DataBinder { return rtn; } + private String stripLastDot(String string) { + if (StringUtils.hasLength(string) && string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + return string; + } + private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) { String name = propertyValue.getName(); String normalizedName = normalizePath(target, name); diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java index c568e855658..ebf73b6b61e 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/PropertiesConfigurationFactoryTests.java @@ -17,13 +17,17 @@ package org.springframework.boot.bind; import java.io.IOException; +import java.util.Collections; import javax.validation.Validation; import javax.validation.constraints.NotNull; import org.junit.Test; import org.springframework.beans.NotWritablePropertyException; +import org.springframework.boot.context.config.RandomValuePropertySource; import org.springframework.context.support.StaticMessageSource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.SystemEnvironmentPropertySource; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.validation.BindException; @@ -110,6 +114,21 @@ public class PropertiesConfigurationFactoryTests { assertEquals("blah", foo.bar); } + @Test + public void testBindWithDashPrefix() throws Exception { + // gh-4045 + this.targetName = "foo-bar"; + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", + Collections.singletonMap("FOO_BAR_NAME", "blah"))); + propertySources.addLast(new RandomValuePropertySource("random")); + setupFactory(); + this.factory.setPropertySources(propertySources); + this.factory.afterPropertiesSet(); + Foo foo = this.factory.getObject(); + assertEquals("blah", foo.name); + } + private Foo createFoo(final String values) throws Exception { setupFactory(); return bindFoo(values);