Skip to content

Commit b4f33ad

Browse files
committed
Consistent java.util.Optional resolution, lenient handling of optional multipart files, correct Servlet 3.0 Part list/array selection
Issue: SPR-13418 Issue: SPR-13849 Issue: SPR-13850 Issue: SPR-13893
1 parent a3a5a03 commit b4f33ad

23 files changed

+816
-447
lines changed

spring-core/src/main/java/org/springframework/core/MethodParameter.java

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -27,17 +27,16 @@
2727
import java.util.Map;
2828

2929
import org.springframework.util.Assert;
30+
import org.springframework.util.ClassUtils;
3031

3132
/**
32-
* Helper class that encapsulates the specification of a method parameter, i.e.
33-
* a {@link Method} or {@link Constructor} plus a parameter index and a nested
34-
* type index for a declared generic type. Useful as a specification object to
35-
* pass along.
33+
* Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method}
34+
* or {@link Constructor} plus a parameter index and a nested type index for a declared generic
35+
* type. Useful as a specification object to pass along.
3636
*
37-
* <p>As of 4.2, there is a {@link org.springframework.core.annotation.SynthesizingMethodParameter
38-
* SynthesizingMethodParameter} subclass available which synthesizes annotations
39-
* with attribute aliases. That subclass is used for web and message endpoint
40-
* processing, in particular.
37+
* <p>As of 4.2, there is a {@link org.springframework.core.annotation.SynthesizingMethodParameter}
38+
* subclass available which synthesizes annotations with attribute aliases. That subclass is used
39+
* for web and message endpoint processing, in particular.
4140
*
4241
* @author Juergen Hoeller
4342
* @author Rob Harrop
@@ -49,6 +48,18 @@
4948
*/
5049
public class MethodParameter {
5150

51+
private static Class<?> javaUtilOptionalClass = null;
52+
53+
static {
54+
try {
55+
javaUtilOptionalClass = ClassUtils.forName("java.util.Optional", MethodParameter.class.getClassLoader());
56+
}
57+
catch (ClassNotFoundException ex) {
58+
// Java 8 not available - Optional references simply not supported then.
59+
}
60+
}
61+
62+
5263
private final Method method;
5364

5465
private final Constructor<?> constructor;
@@ -72,6 +83,8 @@ public class MethodParameter {
7283

7384
private volatile String parameterName;
7485

86+
private volatile MethodParameter nestedMethodParameter;
87+
7588

7689
/**
7790
* Create a new {@code MethodParameter} for the given method, with nesting level 1.
@@ -279,6 +292,44 @@ private Map<Integer, Integer> getTypeIndexesPerLevel() {
279292
return this.typeIndexesPerLevel;
280293
}
281294

295+
/**
296+
* Return a variant of this {@code MethodParameter} which points to the
297+
* same parameter but one nesting level deeper. This is effectively the
298+
* same as {@link #increaseNestingLevel()}, just with an independent
299+
* {@code MethodParameter} object (e.g. in case of the original being cached).
300+
* @since 4.3
301+
*/
302+
public MethodParameter nested() {
303+
if (this.nestedMethodParameter != null) {
304+
return this.nestedMethodParameter;
305+
}
306+
MethodParameter nestedParam = clone();
307+
nestedParam.nestingLevel = this.nestingLevel + 1;
308+
this.nestedMethodParameter = nestedParam;
309+
return nestedParam;
310+
}
311+
312+
/**
313+
* Return whether this method parameter is declared as optiona
314+
* in the form of Java 8's {@link java.util.Optional}.
315+
* @since 4.3
316+
*/
317+
public boolean isOptional() {
318+
return (getParameterType() == javaUtilOptionalClass);
319+
}
320+
321+
/**
322+
* Return a variant of this {@code MethodParameter} which points to
323+
* the same parameter but one nesting level deeper in case of a
324+
* {@link java.util.Optional} declaration.
325+
* @since 4.3
326+
* @see #isOptional()
327+
* @see #nested()
328+
*/
329+
public MethodParameter nestedIfOptional() {
330+
return (isOptional() ? nested() : this);
331+
}
332+
282333

283334
/**
284335
* Set a containing class to resolve the parameter type against.
@@ -370,8 +421,8 @@ else if (type instanceof ParameterizedType) {
370421
/**
371422
* Return the nested generic type of the method/constructor parameter.
372423
* @return the parameter type (never {@code null})
373-
* @see #getNestingLevel()
374424
* @since 4.2
425+
* @see #getNestingLevel()
375426
*/
376427
public Type getNestedGenericParameterType() {
377428
if (this.nestingLevel > 1) {
@@ -526,6 +577,11 @@ public int hashCode() {
526577
return (getMember().hashCode() * 31 + this.parameterIndex);
527578
}
528579

580+
@Override
581+
public MethodParameter clone() {
582+
return new MethodParameter(this);
583+
}
584+
529585

530586
/**
531587
* Create a new MethodParameter for the given method or constructor.

spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -44,6 +44,10 @@ public SynthesizingMethodParameter(Method method, int parameterIndex) {
4444
super(method, parameterIndex);
4545
}
4646

47+
protected SynthesizingMethodParameter(SynthesizingMethodParameter original) {
48+
super(original);
49+
}
50+
4751

4852
@Override
4953
protected <A extends Annotation> A adaptAnnotation(A annotation) {
@@ -55,4 +59,10 @@ protected Annotation[] adaptAnnotationArray(Annotation[] annotations) {
5559
return AnnotationUtils.synthesizeAnnotationArray(annotations, getAnnotatedElement());
5660
}
5761

62+
63+
@Override
64+
public SynthesizingMethodParameter clone() {
65+
return new SynthesizingMethodParameter(this);
66+
}
67+
5868
}

spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -265,6 +265,10 @@ public HandlerMethodParameter(int index) {
265265
super(HandlerMethod.this.bridgedMethod, index);
266266
}
267267

268+
protected HandlerMethodParameter(HandlerMethodParameter original) {
269+
super(original);
270+
}
271+
268272
@Override
269273
public Class<?> getContainingClass() {
270274
return HandlerMethod.this.getBeanType();
@@ -274,6 +278,11 @@ public Class<?> getContainingClass() {
274278
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
275279
return HandlerMethod.this.getMethodAnnotation(annotationType);
276280
}
281+
282+
@Override
283+
public HandlerMethodParameter clone() {
284+
return new HandlerMethodParameter(this);
285+
}
277286
}
278287

279288

@@ -289,10 +298,20 @@ public ReturnValueMethodParameter(Object returnValue) {
289298
this.returnValue = returnValue;
290299
}
291300

301+
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
302+
super(original);
303+
this.returnValue = original.returnValue;
304+
}
305+
292306
@Override
293307
public Class<?> getParameterType() {
294308
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
295309
}
310+
311+
@Override
312+
public ReturnValueMethodParameter clone() {
313+
return new ReturnValueMethodParameter(this);
314+
}
296315
}
297316

298317
}

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -84,24 +84,24 @@ protected AbstractNamedValueMethodArgumentResolver(ConversionService cs, Configu
8484

8585
@Override
8686
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
87-
Class<?> paramType = parameter.getParameterType();
8887
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
88+
MethodParameter nestedParameter = parameter.nestedIfOptional();
8989

90-
Object arg = resolveArgumentInternal(parameter, message, namedValueInfo.name);
90+
Object arg = resolveArgumentInternal(nestedParameter, message, namedValueInfo.name);
9191
if (arg == null) {
9292
if (namedValueInfo.defaultValue != null) {
9393
arg = resolveDefaultValue(namedValueInfo.defaultValue);
9494
}
95-
else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
96-
handleMissingValue(namedValueInfo.name, parameter, message);
95+
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
96+
handleMissingValue(namedValueInfo.name, nestedParameter, message);
9797
}
98-
arg = handleNullValue(namedValueInfo.name, arg, paramType);
98+
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
9999
}
100100
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
101101
arg = resolveDefaultValue(namedValueInfo.defaultValue);
102102
}
103103

104-
if (!ClassUtils.isAssignableValue(paramType, arg)) {
104+
if (!ClassUtils.isAssignableValue(parameter.getParameterType(), arg)) {
105105
arg = this.conversionService.convert(
106106
arg, TypeDescriptor.valueOf(arg.getClass()), new TypeDescriptor(parameter));
107107
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -42,6 +42,7 @@ public DestinationVariableMethodArgumentResolver(ConversionService cs) {
4242
super(cs, null);
4343
}
4444

45+
4546
@Override
4647
public boolean supportsParameter(MethodParameter parameter) {
4748
return parameter.hasParameterAnnotation(DestinationVariable.class);
@@ -58,10 +59,9 @@ protected Object resolveArgumentInternal(MethodParameter parameter, Message<?> m
5859
throws Exception {
5960

6061
@SuppressWarnings("unchecked")
61-
Map<String, String> vars = (Map<String, String>) message.getHeaders().get(
62-
DESTINATION_TEMPLATE_VARIABLES_HEADER);
63-
64-
return (vars != null) ? vars.get(name) : null;
62+
Map<String, String> vars =
63+
(Map<String, String>) message.getHeaders().get(DESTINATION_TEMPLATE_VARIABLES_HEADER);
64+
return (vars != null ? vars.get(name) : null);
6565
}
6666

6767
@Override
@@ -77,4 +77,5 @@ private DestinationVariableNamedValueInfo(DestinationVariable annotation) {
7777
super(annotation.value(), true, ValueConstants.DEFAULT_NONE);
7878
}
7979
}
80-
}
80+
81+
}

spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -272,6 +272,12 @@ public AsyncResultMethodParameter(Object returnValue) {
272272
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
273273
}
274274

275+
protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
276+
super(original);
277+
this.returnValue = original.returnValue;
278+
this.returnType = original.returnType;
279+
}
280+
275281
@Override
276282
public Class<?> getParameterType() {
277283
if (this.returnValue != null) {
@@ -287,6 +293,11 @@ public Class<?> getParameterType() {
287293
public Type getGenericParameterType() {
288294
return this.returnType.getType();
289295
}
296+
297+
@Override
298+
public AsyncResultMethodParameter clone() {
299+
return new AsyncResultMethodParameter(this);
300+
}
290301
}
291302

292303
}

spring-web/src/main/java/org/springframework/web/bind/annotation/RequestPart.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -55,7 +55,6 @@
5555
* @author Arjen Poutsma
5656
* @author Sam Brannen
5757
* @since 3.1
58-
*
5958
* @see RequestParam
6059
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
6160
*/

spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -280,6 +280,10 @@ public HandlerMethodParameter(int index) {
280280
super(HandlerMethod.this.bridgedMethod, index);
281281
}
282282

283+
protected HandlerMethodParameter(HandlerMethodParameter original) {
284+
super(original);
285+
}
286+
283287
@Override
284288
public Class<?> getContainingClass() {
285289
return HandlerMethod.this.getBeanType();
@@ -289,6 +293,11 @@ public Class<?> getContainingClass() {
289293
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
290294
return HandlerMethod.this.getMethodAnnotation(annotationType);
291295
}
296+
297+
@Override
298+
public HandlerMethodParameter clone() {
299+
return new HandlerMethodParameter(this);
300+
}
292301
}
293302

294303

@@ -304,10 +313,20 @@ public ReturnValueMethodParameter(Object returnValue) {
304313
this.returnValue = returnValue;
305314
}
306315

316+
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
317+
super(original);
318+
this.returnValue = original.returnValue;
319+
}
320+
307321
@Override
308322
public Class<?> getParameterType() {
309323
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
310324
}
325+
326+
@Override
327+
public ReturnValueMethodParameter clone() {
328+
return new ReturnValueMethodParameter(this);
329+
}
311330
}
312331

313332
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -63,7 +63,7 @@ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
6363
@Override
6464
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
6565
throw new ServletRequestBindingException("Missing cookie '" + name +
66-
"' for method parameter of type " + parameter.getParameterType().getSimpleName());
66+
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
6767
}
6868

6969

0 commit comments

Comments
 (0)