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 9e6bd0d6476..709d73adcbd 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 @@ -18,6 +18,7 @@ package org.springframework.web.service.invoker; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -83,10 +84,12 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA if (Map.class.isAssignableFrom(parameter.getParameterType())) { Assert.isInstanceOf(Map.class, argument); + parameter = parameter.nested(1); + argument = (argument != null ? argument : Collections.emptyMap()); for (Map.Entry entry : ((Map) argument).entrySet()) { addSingleOrMultipleValues( entry.getKey(), entry.getValue(), false, null, info.label, info.multiValued, - null, requestValues); + parameter, requestValues); } } else { @@ -136,17 +139,20 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA private void addSingleOrMultipleValues( String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, - String valueLabel, boolean supportsMultiValues, @Nullable MethodParameter parameter, + String valueLabel, boolean supportsMultiValues, MethodParameter parameter, HttpRequestValues.Builder requestValues) { if (supportsMultiValues) { - value = (ObjectUtils.isArray(value) ? Arrays.asList((Object[]) value) : value); + if (ObjectUtils.isArray(value)) { + value = Arrays.asList((Object[]) value); + } if (value instanceof Collection elements) { + parameter = parameter.nested(); boolean hasValues = false; for (Object element : elements) { if (element != null) { hasValues = true; - addSingleValue(name, element, false, null, valueLabel, null, requestValues); + addSingleValue(name, element, false, null, valueLabel, parameter, requestValues); } } if (hasValues) { @@ -160,8 +166,8 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA } private void addSingleValue( - String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, String valueLabel, - @Nullable MethodParameter parameter, HttpRequestValues.Builder requestValues) { + String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, + String valueLabel, MethodParameter parameter, HttpRequestValues.Builder requestValues) { if (value instanceof Optional optionalValue) { value = optionalValue.orElse(null); @@ -172,13 +178,11 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA } if (this.conversionService != null && !(value instanceof String)) { - 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); - } + parameter = parameter.nestedIfOptional(); + Class type = parameter.getNestedParameterType(); + value = (type != Object.class && !type.isArray() ? + this.conversionService.convert(value, new TypeDescriptor(parameter), STRING_TARGET_TYPE) : + this.conversionService.convert(value, String.class)); } if (value == null) { @@ -190,7 +194,7 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA logger.trace("Resolved " + valueLabel + " value '" + name + ":" + value + "'"); } - addRequestValue(name, value, requestValues); + addRequestValue(name, value, parameter, requestValues); } /** @@ -200,9 +204,11 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA * will have been converted to a String and may be cast down. * @param name the request value name * @param value the value + * @param parameter the method parameter type, nested if Map, List/array, or Optional * @param requestValues builder to add the request value to */ - protected abstract void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues); + protected abstract void addRequestValue( + String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues); /** diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/CookieValueArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/CookieValueArgumentResolver.java index 9c5ba8fc597..84a00aa1a80 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/CookieValueArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/CookieValueArgumentResolver.java @@ -63,7 +63,9 @@ public class CookieValueArgumentResolver extends AbstractNamedValueArgumentResol } @Override - protected void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues) { + protected void addRequestValue( + String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) { + requestValues.addCookie(name, (String) value); } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/PathVariableArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/PathVariableArgumentResolver.java index 6664ad20d73..041894449b8 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/PathVariableArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/PathVariableArgumentResolver.java @@ -55,7 +55,9 @@ public class PathVariableArgumentResolver extends AbstractNamedValueArgumentReso } @Override - protected void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues) { + protected void addRequestValue( + String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) { + requestValues.setUriVariable(name, (String) value); } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolver.java index c7a9f9f0ef6..9aa75b9dcb8 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolver.java @@ -47,7 +47,9 @@ public class RequestAttributeArgumentResolver extends AbstractNamedValueArgument } @Override - protected void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues) { + protected void addRequestValue( + String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) { + requestValues.addAttribute(name, value); } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolver.java index 7d46b8b1f74..9c0d566c3d6 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolver.java @@ -64,7 +64,9 @@ public class RequestHeaderArgumentResolver extends AbstractNamedValueArgumentRes } @Override - protected void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues) { + protected void addRequestValue( + String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) { + requestValues.addHeader(name, (String) value); } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestParamArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestParamArgumentResolver.java index d7b904db1e2..2c3bb304fd0 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/RequestParamArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/RequestParamArgumentResolver.java @@ -67,7 +67,9 @@ public class RequestParamArgumentResolver extends AbstractNamedValueArgumentReso } @Override - protected void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues) { + protected void addRequestValue( + String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) { + requestValues.addRequestParameter(name, (String) value); } diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java index 28a8399be73..3795aaa4daa 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java @@ -22,8 +22,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; import static java.nio.charset.StandardCharsets.UTF_8; @@ -106,4 +109,21 @@ public class HttpRequestValuesTests { .isEqualTo("/path?param1=1st%20value¶m2=2nd%20value%20A¶m2=2nd%20value%20B"); } + @Test + void requestPart() { + HttpHeaders entityHeaders = new HttpHeaders(); + entityHeaders.add("foo", "bar"); + HttpEntity entity = new HttpEntity<>("body", entityHeaders); + + HttpRequestValues requestValues = HttpRequestValues.builder() + .addRequestPart("form field", "form value") + .addRequestPart("entity", entity) + .build(); + + MultiValueMap> map = (MultiValueMap>) requestValues.getBodyValue(); + assertThat(map).hasSize(2); + assertThat(map.getFirst("form field").getBody()).isEqualTo("form value"); + assertThat(map.getFirst("entity")).isEqualTo(entity); + } + } 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 6ac12d4675b..ea09582c705 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 @@ -240,7 +240,7 @@ class NamedValueArgumentResolverTests { } @Override - protected void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues) { + protected void addRequestValue(String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) { this.testValues.add(name, (String) value); } }