22
22
import java .lang .annotation .Annotation ;
23
23
import java .lang .reflect .Type ;
24
24
import java .util .ArrayList ;
25
+ import java .util .Collection ;
25
26
import java .util .Collections ;
26
27
import java .util .EnumSet ;
27
28
import java .util .LinkedHashSet ;
28
29
import java .util .List ;
30
+ import java .util .Optional ;
29
31
import java .util .Set ;
30
32
import javax .servlet .http .HttpServletRequest ;
31
33
45
47
import org .springframework .http .converter .HttpMessageConverter ;
46
48
import org .springframework .http .converter .HttpMessageNotReadableException ;
47
49
import org .springframework .http .server .ServletServerHttpRequest ;
50
+ import org .springframework .lang .UsesJava8 ;
48
51
import org .springframework .util .Assert ;
49
52
import org .springframework .validation .Errors ;
50
53
import org .springframework .validation .annotation .Validated ;
59
62
*
60
63
* @author Arjen Poutsma
61
64
* @author Rossen Stoyanchev
65
+ * @author Juergen Hoeller
62
66
* @since 3.1
63
67
*/
64
68
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
@@ -128,33 +132,33 @@ protected RequestResponseBodyAdviceChain getAdvice() {
128
132
* reading from the given request.
129
133
* @param <T> the expected type of the argument value to be created
130
134
* @param webRequest the current request
131
- * @param methodParam the method argument
135
+ * @param parameter the method parameter descriptor (may be {@code null})
132
136
* @param paramType the type of the argument value to be created
133
137
* @return the created method argument value
134
138
* @throws IOException if the reading from the request fails
135
139
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
136
140
*/
137
- protected <T > Object readWithMessageConverters (NativeWebRequest webRequest , MethodParameter methodParam ,
141
+ protected <T > Object readWithMessageConverters (NativeWebRequest webRequest , MethodParameter parameter ,
138
142
Type paramType ) throws IOException , HttpMediaTypeNotSupportedException , HttpMessageNotReadableException {
139
143
140
144
HttpInputMessage inputMessage = createInputMessage (webRequest );
141
- return readWithMessageConverters (inputMessage , methodParam , paramType );
145
+ return readWithMessageConverters (inputMessage , parameter , paramType );
142
146
}
143
147
144
148
/**
145
149
* Create the method argument value of the expected parameter type by reading
146
150
* from the given HttpInputMessage.
147
151
* @param <T> the expected type of the argument value to be created
148
152
* @param inputMessage the HTTP input message representing the current request
149
- * @param param the method parameter descriptor (may be {@code null})
153
+ * @param parameter the method parameter descriptor (may be {@code null})
150
154
* @param targetType the target type, not necessarily the same as the method
151
155
* parameter type, e.g. for {@code HttpEntity<String>}.
152
156
* @return the created method argument value
153
157
* @throws IOException if the reading from the request fails
154
158
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
155
159
*/
156
160
@ SuppressWarnings ("unchecked" )
157
- protected <T > Object readWithMessageConverters (HttpInputMessage inputMessage , MethodParameter param ,
161
+ protected <T > Object readWithMessageConverters (HttpInputMessage inputMessage , MethodParameter parameter ,
158
162
Type targetType ) throws IOException , HttpMediaTypeNotSupportedException , HttpMessageNotReadableException {
159
163
160
164
MediaType contentType ;
@@ -170,11 +174,11 @@ protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, Me
170
174
contentType = MediaType .APPLICATION_OCTET_STREAM ;
171
175
}
172
176
173
- Class <?> contextClass = (param != null ? param .getContainingClass () : null );
177
+ Class <?> contextClass = (parameter != null ? parameter .getContainingClass () : null );
174
178
Class <T > targetClass = (targetType instanceof Class ? (Class <T >) targetType : null );
175
179
if (targetClass == null ) {
176
- ResolvableType resolvableType = (param != null ?
177
- ResolvableType .forMethodParameter (param ) : ResolvableType .forType (targetType ));
180
+ ResolvableType resolvableType = (parameter != null ?
181
+ ResolvableType .forMethodParameter (parameter ) : ResolvableType .forType (targetType ));
178
182
targetClass = (Class <T >) resolvableType .resolve ();
179
183
}
180
184
@@ -193,13 +197,12 @@ protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, Me
193
197
logger .debug ("Read [" + targetType + "] as \" " + contentType + "\" with [" + converter + "]" );
194
198
}
195
199
if (inputMessage .getBody () != null ) {
196
- inputMessage = getAdvice ().beforeBodyRead (inputMessage , param , targetType , converterType );
200
+ inputMessage = getAdvice ().beforeBodyRead (inputMessage , parameter , targetType , converterType );
197
201
body = genericConverter .read (targetType , contextClass , inputMessage );
198
- body = getAdvice ().afterBodyRead (body , inputMessage , param , targetType , converterType );
202
+ body = getAdvice ().afterBodyRead (body , inputMessage , parameter , targetType , converterType );
199
203
}
200
204
else {
201
- body = null ;
202
- body = getAdvice ().handleEmptyBody (body , inputMessage , param , targetType , converterType );
205
+ body = getAdvice ().handleEmptyBody (null , inputMessage , parameter , targetType , converterType );
203
206
}
204
207
break ;
205
208
}
@@ -210,13 +213,12 @@ else if (targetClass != null) {
210
213
logger .debug ("Read [" + targetType + "] as \" " + contentType + "\" with [" + converter + "]" );
211
214
}
212
215
if (inputMessage .getBody () != null ) {
213
- inputMessage = getAdvice ().beforeBodyRead (inputMessage , param , targetType , converterType );
216
+ inputMessage = getAdvice ().beforeBodyRead (inputMessage , parameter , targetType , converterType );
214
217
body = ((HttpMessageConverter <T >) converter ).read (targetClass , inputMessage );
215
- body = getAdvice ().afterBodyRead (body , inputMessage , param , targetType , converterType );
218
+ body = getAdvice ().afterBodyRead (body , inputMessage , parameter , targetType , converterType );
216
219
}
217
220
else {
218
- body = null ;
219
- body = getAdvice ().handleEmptyBody (body , inputMessage , param , targetType , converterType );
221
+ body = getAdvice ().handleEmptyBody (null , inputMessage , parameter , targetType , converterType );
220
222
}
221
223
break ;
222
224
}
@@ -254,12 +256,12 @@ protected ServletServerHttpRequest createInputMessage(NativeWebRequest webReques
254
256
* Spring's {@link org.springframework.validation.annotation.Validated},
255
257
* and custom annotations whose name starts with "Valid".
256
258
* @param binder the DataBinder to be used
257
- * @param methodParam the method parameter
258
- * @see #isBindExceptionRequired
259
+ * @param parameter the method parameter descriptor
259
260
* @since 4.1.5
261
+ * @see #isBindExceptionRequired
260
262
*/
261
- protected void validateIfApplicable (WebDataBinder binder , MethodParameter methodParam ) {
262
- Annotation [] annotations = methodParam .getParameterAnnotations ();
263
+ protected void validateIfApplicable (WebDataBinder binder , MethodParameter parameter ) {
264
+ Annotation [] annotations = parameter .getParameterAnnotations ();
263
265
for (Annotation ann : annotations ) {
264
266
Validated validatedAnn = AnnotationUtils .getAnnotation (ann , Validated .class );
265
267
if (validatedAnn != null || ann .annotationType ().getSimpleName ().startsWith ("Valid" )) {
@@ -274,17 +276,28 @@ protected void validateIfApplicable(WebDataBinder binder, MethodParameter method
274
276
/**
275
277
* Whether to raise a fatal bind exception on validation errors.
276
278
* @param binder the data binder used to perform data binding
277
- * @param methodParam the method argument
279
+ * @param parameter the method parameter descriptor
278
280
* @return {@code true} if the next method argument is not of type {@link Errors}
279
281
* @since 4.1.5
280
282
*/
281
- protected boolean isBindExceptionRequired (WebDataBinder binder , MethodParameter methodParam ) {
282
- int i = methodParam .getParameterIndex ();
283
- Class <?>[] paramTypes = methodParam .getMethod ().getParameterTypes ();
283
+ protected boolean isBindExceptionRequired (WebDataBinder binder , MethodParameter parameter ) {
284
+ int i = parameter .getParameterIndex ();
285
+ Class <?>[] paramTypes = parameter .getMethod ().getParameterTypes ();
284
286
boolean hasBindingResult = (paramTypes .length > (i + 1 ) && Errors .class .isAssignableFrom (paramTypes [i + 1 ]));
285
287
return !hasBindingResult ;
286
288
}
287
289
290
+ /**
291
+ * Adapt the given argument against the method parameter, if necessary.
292
+ * @param arg the resolved argument
293
+ * @param parameter the method parameter descriptor
294
+ * @return the adapted argument, or the original resolved argument as-is
295
+ * @since 4.3.5
296
+ */
297
+ protected Object adaptArgumentIfNecessary (Object arg , MethodParameter parameter ) {
298
+ return (parameter .isOptional () ? OptionalResolver .resolveValue (arg ) : arg );
299
+ }
300
+
288
301
289
302
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {
290
303
@@ -335,4 +348,20 @@ public HttpMethod getMethod() {
335
348
}
336
349
}
337
350
351
+
352
+ /**
353
+ * Inner class to avoid hard-coded dependency on Java 8 Optional type...
354
+ */
355
+ @ UsesJava8
356
+ private static class OptionalResolver {
357
+
358
+ public static Object resolveValue (Object value ) {
359
+ if (value == null || (value instanceof Collection && ((Collection ) value ).isEmpty ()) ||
360
+ (value instanceof Object [] && ((Object []) value ).length == 0 )) {
361
+ return Optional .empty ();
362
+ }
363
+ return Optional .of (value );
364
+ }
365
+ }
366
+
338
367
}
0 commit comments