Skip to content

Commit 50b9542

Browse files
committed
Apply handleMissingValue in case of null conversion result as well
Closes gh-23939
1 parent 2a34c0e commit 50b9542

File tree

5 files changed

+64
-27
lines changed

5 files changed

+64
-27
lines changed

spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/AbstractNamedValueMethodArgumentResolver.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -82,7 +82,6 @@ protected AbstractNamedValueMethodArgumentResolver(ConversionService conversionS
8282

8383
@Override
8484
public Object resolveArgumentValue(MethodParameter parameter, Message<?> message) {
85-
8685
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
8786
MethodParameter nestedParameter = parameter.nestedIfOptional();
8887

@@ -108,6 +107,11 @@ else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
108107

109108
if (parameter != nestedParameter || !ClassUtils.isAssignableValue(parameter.getParameterType(), arg)) {
110109
arg = this.conversionService.convert(arg, TypeDescriptor.forObject(arg), new TypeDescriptor(parameter));
110+
// Check for null value after conversion of incoming argument value
111+
if (arg == null && namedValueInfo.defaultValue == null &&
112+
namedValueInfo.required && !nestedParameter.isOptional()) {
113+
handleMissingValue(namedValueInfo.name, nestedParameter, message);
114+
}
111115
}
112116

113117
return arg;
@@ -144,10 +148,9 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
144148
if (info.name.isEmpty()) {
145149
name = parameter.getParameterName();
146150
if (name == null) {
147-
Class<?> type = parameter.getParameterType();
148151
throw new IllegalArgumentException(
149-
"Name for argument of type [" + type.getName() + "] not specified, " +
150-
"and parameter name information not found in class file either.");
152+
"Name for argument of type [" + parameter.getNestedParameterType().getName() +
153+
"] not specified, and parameter name information not found in class file either.");
151154
}
152155
}
153156
return new NamedValueInfo(name, info.required,

spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractNamedValueMethodArgumentResolver.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,8 +80,8 @@ protected AbstractNamedValueMethodArgumentResolver(ConversionService conversionS
8080
// Possibly remove after discussion in gh-23882.
8181

8282
//noinspection ConstantConditions
83-
this.conversionService = conversionService != null ?
84-
conversionService : DefaultConversionService.getSharedInstance();
83+
this.conversionService = (conversionService != null ?
84+
conversionService : DefaultConversionService.getSharedInstance());
8585

8686
this.configurableBeanFactory = beanFactory;
8787
this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null);
@@ -116,6 +116,11 @@ else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
116116

117117
if (parameter != nestedParameter || !ClassUtils.isAssignableValue(parameter.getParameterType(), arg)) {
118118
arg = this.conversionService.convert(arg, TypeDescriptor.forObject(arg), new TypeDescriptor(parameter));
119+
// Check for null value after conversion of incoming argument value
120+
if (arg == null && namedValueInfo.defaultValue == null &&
121+
namedValueInfo.required && !nestedParameter.isOptional()) {
122+
handleMissingValue(namedValueInfo.name, nestedParameter, message);
123+
}
119124
}
120125

121126
handleResolvedValue(arg, namedValueInfo.name, parameter, message);
@@ -154,10 +159,9 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
154159
if (info.name.isEmpty()) {
155160
name = parameter.getParameterName();
156161
if (name == null) {
157-
Class<?> type = parameter.getParameterType();
158162
throw new IllegalArgumentException(
159-
"Name for argument of type [" + type.getName() + "] not specified, " +
160-
"and parameter name information not found in class file either.");
163+
"Name for argument of type [" + parameter.getNestedParameterType().getName() +
164+
"] not specified, and parameter name information not found in class file either.");
161165
}
162166
}
163167
return new NamedValueInfo(name, info.required,

spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -99,7 +99,7 @@ public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAn
9999
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
100100
MethodParameter nestedParameter = parameter.nestedIfOptional();
101101

102-
Object resolvedName = resolveStringValue(namedValueInfo.name);
102+
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
103103
if (resolvedName == null) {
104104
throw new IllegalArgumentException(
105105
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
@@ -108,15 +108,15 @@ public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAn
108108
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
109109
if (arg == null) {
110110
if (namedValueInfo.defaultValue != null) {
111-
arg = resolveStringValue(namedValueInfo.defaultValue);
111+
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
112112
}
113113
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
114114
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
115115
}
116116
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
117117
}
118118
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
119-
arg = resolveStringValue(namedValueInfo.defaultValue);
119+
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
120120
}
121121

122122
if (binderFactory != null) {
@@ -132,6 +132,11 @@ else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
132132
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
133133
namedValueInfo.name, parameter, ex.getCause());
134134
}
135+
// Check for null value after conversion of incoming argument value
136+
if (arg == null && namedValueInfo.defaultValue == null &&
137+
namedValueInfo.required && !nestedParameter.isOptional()) {
138+
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
139+
}
135140
}
136141

137142
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
@@ -169,8 +174,8 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
169174
name = parameter.getParameterName();
170175
if (name == null) {
171176
throw new IllegalArgumentException(
172-
"Name for argument type [" + parameter.getNestedParameterType().getName() +
173-
"] not available, and parameter name information not found in class file either.");
177+
"Name for argument of type [" + parameter.getNestedParameterType().getName() +
178+
"] not specified, and parameter name information not found in class file either.");
174179
}
175180
}
176181
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
@@ -182,13 +187,13 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
182187
* potentially containing placeholders and expressions.
183188
*/
184189
@Nullable
185-
private Object resolveStringValue(String value) {
186-
if (this.configurableBeanFactory == null) {
190+
private Object resolveEmbeddedValuesAndExpressions(String value) {
191+
if (this.configurableBeanFactory == null || this.expressionContext == null) {
187192
return value;
188193
}
189194
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
190195
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
191-
if (exprResolver == null || this.expressionContext == null) {
196+
if (exprResolver == null) {
192197
return value;
193198
}
194199
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);

spring-web/src/test/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolverTests.java

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.springframework.core.annotation.SynthesizingMethodParameter;
3232
import org.springframework.format.support.DefaultFormattingConversionService;
3333
import org.springframework.util.ReflectionUtils;
34+
import org.springframework.web.bind.MissingRequestHeaderException;
3435
import org.springframework.web.bind.ServletRequestBindingException;
3536
import org.springframework.web.bind.annotation.RequestHeader;
3637
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
@@ -66,6 +67,7 @@ class RequestHeaderMethodArgumentResolverTests {
6667
private MethodParameter paramDate;
6768
private MethodParameter paramInstant;
6869
private MethodParameter paramUuid;
70+
private MethodParameter paramUuidOptional;
6971

7072
private MockHttpServletRequest servletRequest;
7173

@@ -90,6 +92,7 @@ void setup() throws Exception {
9092
paramDate = new SynthesizingMethodParameter(method, 7);
9193
paramInstant = new SynthesizingMethodParameter(method, 8);
9294
paramUuid = new SynthesizingMethodParameter(method, 9);
95+
paramUuidOptional = new SynthesizingMethodParameter(method, 10);
9396

9497
servletRequest = new MockHttpServletRequest();
9598
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
@@ -265,7 +268,28 @@ private void uuidConversionWithEmptyOrBlankValue(String uuid) throws Exception {
265268

266269
ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
267270
bindingInitializer.setConversionService(new DefaultFormattingConversionService());
268-
Object result = resolver.resolveArgument(paramUuid, null, webRequest,
271+
272+
assertThatExceptionOfType(MissingRequestHeaderException.class).isThrownBy(() ->
273+
resolver.resolveArgument(paramUuid, null, webRequest,
274+
new DefaultDataBinderFactory(bindingInitializer)));
275+
}
276+
277+
@Test
278+
void uuidConversionWithEmptyValueOptional() throws Exception {
279+
uuidConversionWithEmptyOrBlankValueOptional("");
280+
}
281+
282+
@Test
283+
void uuidConversionWithBlankValueOptional() throws Exception {
284+
uuidConversionWithEmptyOrBlankValueOptional(" ");
285+
}
286+
287+
private void uuidConversionWithEmptyOrBlankValueOptional(String uuid) throws Exception {
288+
servletRequest.addHeader("name", uuid);
289+
290+
ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
291+
bindingInitializer.setConversionService(new DefaultFormattingConversionService());
292+
Object result = resolver.resolveArgument(paramUuidOptional, null, webRequest,
269293
new DefaultDataBinderFactory(bindingInitializer));
270294

271295
assertThat(result).isNull();
@@ -282,7 +306,8 @@ void params(
282306
@RequestHeader("name") Map<?, ?> unsupported,
283307
@RequestHeader("name") Date dateParam,
284308
@RequestHeader("name") Instant instantParam,
285-
@RequestHeader("name") UUID uuid) {
309+
@RequestHeader("name") UUID uuid,
310+
@RequestHeader(name = "name", required = false) UUID uuidOptional) {
286311
}
287312

288313
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -144,9 +144,9 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
144144
if (info.name.isEmpty()) {
145145
name = parameter.getParameterName();
146146
if (name == null) {
147-
String type = parameter.getNestedParameterType().getName();
148-
throw new IllegalArgumentException("Name for argument type [" + type + "] not " +
149-
"available, and parameter name information not found in class file either.");
147+
throw new IllegalArgumentException(
148+
"Name for argument of type [" + parameter.getNestedParameterType().getName() +
149+
"] not specified, and parameter name information not found in class file either.");
150150
}
151151
}
152152
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);

0 commit comments

Comments
 (0)