From 3b298cba5df1b7b39dd32cc78f7da2907be36924 Mon Sep 17 00:00:00 2001 From: dreis2211 Date: Wed, 29 Jul 2020 17:41:31 +0200 Subject: [PATCH 1/3] Allow DurationFormat and PeriodFormat to be used on parameters Closes gh-22644 --- .../boot/convert/DurationFormat.java | 4 +-- .../boot/convert/PeriodFormat.java | 2 +- .../ConfigurationPropertiesTests.java | 27 +++++++++++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java index 116a780833d3..3f53a2368bb8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -30,7 +30,7 @@ * @author Phillip Webb * @since 2.0.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DurationFormat { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java index 45d45183b812..48f6537dbce8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java @@ -31,7 +31,7 @@ * @author Edson Chávez * @since 2.3.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PeriodFormat { 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 e1e289baf67e..e167e5002c29 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 @@ -56,7 +56,11 @@ import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.convert.DataSizeUnit; +import org.springframework.boot.convert.DurationFormat; +import org.springframework.boot.convert.DurationStyle; import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.convert.PeriodFormat; +import org.springframework.boot.convert.PeriodStyle; import org.springframework.boot.convert.PeriodUnit; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; @@ -781,13 +785,16 @@ void loadWhenBindingToConstructorParametersWithCustomDataUnitShouldBind() { source.put("test.duration", "12"); source.put("test.size", "13"); source.put("test.period", "14"); + source.put("test.formattedDuration", "15d"); + source.put("test.formattedPeriod", "16y"); sources.addLast(new MapPropertySource("test", source)); load(ConstructorParameterWithUnitConfiguration.class); ConstructorParameterWithUnitProperties bean = this.context .getBean(ConstructorParameterWithUnitProperties.class); assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(12)); assertThat(bean.getSize()).isEqualTo(DataSize.ofMegabytes(13)); - assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(14)); + assertThat(bean.getFormattedDuration()).isEqualTo(Duration.ofDays(15)); + assertThat(bean.getFormattedPeriod()).isEqualTo(Period.ofYears(16)); } @Test @@ -1985,12 +1992,20 @@ static class ConstructorParameterWithUnitProperties { private final Period period; + private final Duration formattedDuration; + + private final Period formattedPeriod; + ConstructorParameterWithUnitProperties(@DefaultValue("2") @DurationUnit(ChronoUnit.DAYS) Duration duration, @DefaultValue("3") @DataSizeUnit(DataUnit.MEGABYTES) DataSize size, - @DefaultValue("4") @PeriodUnit(ChronoUnit.YEARS) Period period) { + @DefaultValue("4") @PeriodUnit(ChronoUnit.YEARS) Period period, + @DefaultValue("5") @DurationFormat(DurationStyle.SIMPLE) Duration formattedDuration, + @DefaultValue("6") @PeriodFormat(PeriodStyle.SIMPLE) Period formattedPeriod) { this.size = size; this.duration = duration; this.period = period; + this.formattedDuration = formattedDuration; + this.formattedPeriod = formattedPeriod; } Duration getDuration() { @@ -2005,6 +2020,14 @@ Period getPeriod() { return this.period; } + public Duration getFormattedDuration() { + return this.formattedDuration; + } + + public Period getFormattedPeriod() { + return this.formattedPeriod; + } + } @ConstructorBinding From 05605ed4dd16f46d48c9236dceb6864cf8256cb6 Mon Sep 17 00:00:00 2001 From: dreis2211 Date: Wed, 29 Jul 2020 17:51:40 +0200 Subject: [PATCH 2/3] Fix method visibility --- .../boot/context/properties/ConfigurationPropertiesTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e167e5002c29..d6bce1007416 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 @@ -2020,11 +2020,11 @@ Period getPeriod() { return this.period; } - public Duration getFormattedDuration() { + Duration getFormattedDuration() { return this.formattedDuration; } - public Period getFormattedPeriod() { + Period getFormattedPeriod() { return this.formattedPeriod; } From 584b4149bd68da6eead5611087c8fe9669647702 Mon Sep 17 00:00:00 2001 From: dreis2211 Date: Wed, 29 Jul 2020 19:32:53 +0200 Subject: [PATCH 3/3] Extend testing for custom data format parameters --- .../ConfigurationPropertiesTests.java | 92 +++++++++++++++---- 1 file changed, 75 insertions(+), 17 deletions(-) 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 d6bce1007416..346e7733260a 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 @@ -785,16 +785,13 @@ void loadWhenBindingToConstructorParametersWithCustomDataUnitShouldBind() { source.put("test.duration", "12"); source.put("test.size", "13"); source.put("test.period", "14"); - source.put("test.formattedDuration", "15d"); - source.put("test.formattedPeriod", "16y"); sources.addLast(new MapPropertySource("test", source)); load(ConstructorParameterWithUnitConfiguration.class); ConstructorParameterWithUnitProperties bean = this.context .getBean(ConstructorParameterWithUnitProperties.class); assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(12)); assertThat(bean.getSize()).isEqualTo(DataSize.ofMegabytes(13)); - assertThat(bean.getFormattedDuration()).isEqualTo(Duration.ofDays(15)); - assertThat(bean.getFormattedPeriod()).isEqualTo(Period.ofYears(16)); + assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(14)); } @Test @@ -815,6 +812,53 @@ void loadWhenBindingToConstructorParametersWithDefaultDataUnitShouldBind() { assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(4)); } + @Test + void loadWhenBindingToConstructorParametersWithCustomDataFormatShouldBind() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + Map source = new HashMap<>(); + source.put("test.duration", "12d"); + source.put("test.period", "13y"); + sources.addLast(new MapPropertySource("test", source)); + load(ConstructorParameterWithFormatConfiguration.class); + ConstructorParameterWithFormatProperties bean = this.context + .getBean(ConstructorParameterWithFormatProperties.class); + assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(12)); + assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(13)); + } + + @Test + void loadWhenBindingToConstructorParametersWithNotMatchingCustomDurationFormatShouldFail() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + Map source = new HashMap<>(); + source.put("test.duration", "P12D"); + sources.addLast(new MapPropertySource("test", source)); + assertThatExceptionOfType(Exception.class) + .isThrownBy(() -> load(ConstructorParameterWithFormatConfiguration.class)).satisfies((ex) -> { + assertThat(ex).hasCauseInstanceOf(BindException.class); + }); + } + + @Test + void loadWhenBindingToConstructorParametersWithNotMatchingCustomPeriodFormatShouldFail() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + Map source = new HashMap<>(); + source.put("test.period", "P12D"); + sources.addLast(new MapPropertySource("test", source)); + assertThatExceptionOfType(Exception.class) + .isThrownBy(() -> load(ConstructorParameterWithFormatConfiguration.class)).satisfies((ex) -> { + assertThat(ex).hasCauseInstanceOf(BindException.class); + }); + } + + @Test + void loadWhenBindingToConstructorParametersWithDefaultDataFormatShouldBind() { + load(ConstructorParameterWithFormatConfiguration.class); + ConstructorParameterWithFormatProperties bean = this.context + .getBean(ConstructorParameterWithFormatProperties.class); + assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(2)); + assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(3)); + } + @Test void loadWhenBindingToConstructorParametersShouldValidate() { assertThatExceptionOfType(Exception.class) @@ -1992,20 +2036,12 @@ static class ConstructorParameterWithUnitProperties { private final Period period; - private final Duration formattedDuration; - - private final Period formattedPeriod; - ConstructorParameterWithUnitProperties(@DefaultValue("2") @DurationUnit(ChronoUnit.DAYS) Duration duration, @DefaultValue("3") @DataSizeUnit(DataUnit.MEGABYTES) DataSize size, - @DefaultValue("4") @PeriodUnit(ChronoUnit.YEARS) Period period, - @DefaultValue("5") @DurationFormat(DurationStyle.SIMPLE) Duration formattedDuration, - @DefaultValue("6") @PeriodFormat(PeriodStyle.SIMPLE) Period formattedPeriod) { + @DefaultValue("4") @PeriodUnit(ChronoUnit.YEARS) Period period) { this.size = size; this.duration = duration; this.period = period; - this.formattedDuration = formattedDuration; - this.formattedPeriod = formattedPeriod; } Duration getDuration() { @@ -2020,12 +2056,29 @@ Period getPeriod() { return this.period; } - Duration getFormattedDuration() { - return this.formattedDuration; + } + + @ConstructorBinding + @ConfigurationProperties(prefix = "test") + static class ConstructorParameterWithFormatProperties { + + private final Duration duration; + + private final Period period; + + ConstructorParameterWithFormatProperties( + @DefaultValue("2d") @DurationFormat(DurationStyle.SIMPLE) Duration duration, + @DefaultValue("3y") @PeriodFormat(PeriodStyle.SIMPLE) Period period) { + this.duration = duration; + this.period = period; + } + + Duration getDuration() { + return this.duration; } - Period getFormattedPeriod() { - return this.formattedPeriod; + Period getPeriod() { + return this.period; } } @@ -2058,6 +2111,11 @@ static class ConstructorParameterWithUnitConfiguration { } + @EnableConfigurationProperties(ConstructorParameterWithFormatProperties.class) + static class ConstructorParameterWithFormatConfiguration { + + } + @EnableConfigurationProperties(ConstructorParameterValidatedProperties.class) static class ConstructorParameterValidationConfiguration {