From 5192d99fa41d1488f1cb5994082b6ab5d795a11f Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 21 Sep 2022 16:18:39 +0100 Subject: [PATCH] Support formatting annotations on HTTP service interface Closes gh-29095 --- .../AbstractNamedValueArgumentResolver.java | 24 +++++++++++++------ .../NamedValueArgumentResolverTests.java | 11 +++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractNamedValueArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractNamedValueArgumentResolver.java index c24421bf9c6..9e6bd0d6476 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractNamedValueArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractNamedValueArgumentResolver.java @@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -41,6 +42,8 @@ import org.springframework.web.bind.annotation.ValueConstants; */ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceArgumentResolver { + private static final TypeDescriptor STRING_TARGET_TYPE = TypeDescriptor.valueOf(String.class); + protected final Log logger = LogFactory.getLog(getClass()); @@ -83,13 +86,13 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA for (Map.Entry entry : ((Map) argument).entrySet()) { addSingleOrMultipleValues( entry.getKey(), entry.getValue(), false, null, info.label, info.multiValued, - requestValues); + null, requestValues); } } else { addSingleOrMultipleValues( info.name, argument, info.required, info.defaultValue, info.label, info.multiValued, - requestValues); + parameter, requestValues); } return true; @@ -133,7 +136,8 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA private void addSingleOrMultipleValues( String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, - String valueLabel, boolean supportsMultiValues, HttpRequestValues.Builder requestValues) { + String valueLabel, boolean supportsMultiValues, @Nullable MethodParameter parameter, + HttpRequestValues.Builder requestValues) { if (supportsMultiValues) { value = (ObjectUtils.isArray(value) ? Arrays.asList((Object[]) value) : value); @@ -142,7 +146,7 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA for (Object element : elements) { if (element != null) { hasValues = true; - addSingleValue(name, element, false, null, valueLabel, requestValues); + addSingleValue(name, element, false, null, valueLabel, null, requestValues); } } if (hasValues) { @@ -152,12 +156,12 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA } } - addSingleValue(name, value, required, defaultValue, valueLabel, requestValues); + addSingleValue(name, value, required, defaultValue, valueLabel, parameter, requestValues); } private void addSingleValue( String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, String valueLabel, - HttpRequestValues.Builder requestValues) { + @Nullable MethodParameter parameter, HttpRequestValues.Builder requestValues) { if (value instanceof Optional optionalValue) { value = optionalValue.orElse(null); @@ -168,7 +172,13 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA } if (this.conversionService != null && !(value instanceof String)) { - value = this.conversionService.convert(value, String.class); + parameter = (parameter != null ? parameter.nestedIfOptional() : null); + if (parameter != null && parameter.getNestedParameterType() != Object.class) { + value = this.conversionService.convert(value, new TypeDescriptor(parameter), STRING_TARGET_TYPE); + } + else { + value = this.conversionService.convert(value, String.class); + } } if (value == null) { diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java index 25b57277894..f2029c47c84 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java @@ -21,6 +21,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.time.LocalDate; import java.util.List; import java.util.Map; import java.util.Optional; @@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AliasFor; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.lang.Nullable; import org.springframework.util.LinkedMultiValueMap; @@ -73,6 +75,12 @@ class NamedValueArgumentResolverTests { assertTestValue("value", "test"); } + @Test // gh-29095 + void dateTestValue() { + this.service.executeDate(LocalDate.of(2022, 9, 16)); + assertTestValue("value", "2022-09-16"); + } + @Test void objectTestValue() { this.service.execute(Boolean.TRUE); @@ -174,6 +182,9 @@ class NamedValueArgumentResolverTests { @GetExchange void executeString(@TestValue String value); + @GetExchange + void executeDate(@TestValue @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate value); + @GetExchange void execute(@TestValue Object value);