From b5d6b9270bbd17e4226d39d25ef32d429f635c55 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 25 Mar 2021 21:30:39 +0000 Subject: [PATCH] missingAfterConversion flag for missing request values Closes gh-26679 --- .../bind/MissingMatrixVariableException.java | 24 +++++++-- .../bind/MissingPathVariableException.java | 24 +++++++-- .../bind/MissingRequestCookieException.java | 24 +++++++-- .../bind/MissingRequestHeaderException.java | 24 +++++++-- .../bind/MissingRequestValueException.java | 50 +++++++++++++++++++ ...ssingServletRequestParameterException.java | 23 +++++++-- ...ractCookieValueMethodArgumentResolver.java | 9 +++- ...tractNamedValueMethodArgumentResolver.java | 17 ++++++- .../RequestHeaderMethodArgumentResolver.java | 8 ++- .../RequestParamMethodArgumentResolver.java | 18 ++++++- .../MatrixVariableMethodArgumentResolver.java | 8 ++- .../PathVariableMethodArgumentResolver.java | 9 +++- 12 files changed, 206 insertions(+), 32 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/bind/MissingRequestValueException.java diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java index 3074a5c317a4..39d038aae4c4 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ * @see MissingPathVariableException */ @SuppressWarnings("serial") -public class MissingMatrixVariableException extends ServletRequestBindingException { +public class MissingMatrixVariableException extends MissingRequestValueException { private final String variableName; @@ -41,7 +41,20 @@ public class MissingMatrixVariableException extends ServletRequestBindingExcepti * @param parameter the method parameter */ public MissingMatrixVariableException(String variableName, MethodParameter parameter) { - super(""); + this(variableName, parameter, false); + } + + /** + * Constructor for use when a value was present but converted to {@code null}. + * @param variableName the name of the missing matrix variable + * @param parameter the method parameter + * @param missingAfterConversion whether the value became null after conversion + * @since 5.3.6 + */ + public MissingMatrixVariableException( + String variableName, MethodParameter parameter, boolean missingAfterConversion) { + + super("", missingAfterConversion); this.variableName = variableName; this.parameter = parameter; } @@ -49,8 +62,9 @@ public MissingMatrixVariableException(String variableName, MethodParameter param @Override public String getMessage() { - return "Missing matrix variable '" + this.variableName + - "' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName(); + return "Required matrix variable '" + this.variableName + "' for method parameter type " + + this.parameter.getNestedParameterType().getSimpleName() + " is " + + (isMissingAfterConversion() ? "present but converted to null" : "not present"); } /** diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java index 191a27d4715a..1fd5e3f4ab30 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ * @see MissingMatrixVariableException */ @SuppressWarnings("serial") -public class MissingPathVariableException extends ServletRequestBindingException { +public class MissingPathVariableException extends MissingRequestValueException { private final String variableName; @@ -43,7 +43,20 @@ public class MissingPathVariableException extends ServletRequestBindingException * @param parameter the method parameter */ public MissingPathVariableException(String variableName, MethodParameter parameter) { - super(""); + this(variableName, parameter, false); + } + + /** + * Constructor for use when a value was present but converted to {@code null}. + * @param variableName the name of the missing path variable + * @param parameter the method parameter + * @param missingAfterConversion whether the value became null after conversion + * @since 5.3.6 + */ + public MissingPathVariableException( + String variableName, MethodParameter parameter, boolean missingAfterConversion) { + + super("", missingAfterConversion); this.variableName = variableName; this.parameter = parameter; } @@ -51,8 +64,9 @@ public MissingPathVariableException(String variableName, MethodParameter paramet @Override public String getMessage() { - return "Missing URI template variable '" + this.variableName + - "' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName(); + return "Required URI template variable '" + this.variableName + "' for method parameter type " + + this.parameter.getNestedParameterType().getSimpleName() + " is " + + (isMissingAfterConversion() ? "present but converted to null" : "not present"); } /** diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java index 7a7ea209127a..fd113c2864a5 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ * @see MissingRequestHeaderException */ @SuppressWarnings("serial") -public class MissingRequestCookieException extends ServletRequestBindingException { +public class MissingRequestCookieException extends MissingRequestValueException { private final String cookieName; @@ -41,7 +41,20 @@ public class MissingRequestCookieException extends ServletRequestBindingExceptio * @param parameter the method parameter */ public MissingRequestCookieException(String cookieName, MethodParameter parameter) { - super(""); + this(cookieName, parameter, false); + } + + /** + * Constructor for use when a value was present but converted to {@code null}. + * @param cookieName the name of the missing request cookie + * @param parameter the method parameter + * @param missingAfterConversion whether the value became null after conversion + * @since 5.3.6 + */ + public MissingRequestCookieException( + String cookieName, MethodParameter parameter, boolean missingAfterConversion) { + + super("", missingAfterConversion); this.cookieName = cookieName; this.parameter = parameter; } @@ -49,8 +62,9 @@ public MissingRequestCookieException(String cookieName, MethodParameter paramete @Override public String getMessage() { - return "Missing cookie '" + this.cookieName + - "' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName(); + return "Required cookie '" + this.cookieName + "' for method parameter type " + + this.parameter.getNestedParameterType().getSimpleName() + " is " + + (isMissingAfterConversion() ? "present but converted to null" : "not present"); } /** diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java index eb3bf660ea64..e60dfb169adb 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ * @see MissingRequestCookieException */ @SuppressWarnings("serial") -public class MissingRequestHeaderException extends ServletRequestBindingException { +public class MissingRequestHeaderException extends MissingRequestValueException { private final String headerName; @@ -41,7 +41,20 @@ public class MissingRequestHeaderException extends ServletRequestBindingExceptio * @param parameter the method parameter */ public MissingRequestHeaderException(String headerName, MethodParameter parameter) { - super(""); + this(headerName, parameter, false); + } + + /** + * Constructor for use when a value was present but converted to {@code null}. + * @param headerName the name of the missing request header + * @param parameter the method parameter + * @param missingAfterConversion whether the value became null after conversion + * @since 5.3.6 + */ + public MissingRequestHeaderException( + String headerName, MethodParameter parameter, boolean missingAfterConversion) { + + super("", missingAfterConversion); this.headerName = headerName; this.parameter = parameter; } @@ -49,8 +62,9 @@ public MissingRequestHeaderException(String headerName, MethodParameter paramete @Override public String getMessage() { - return "Missing request header '" + this.headerName + - "' for method parameter of type " + this.parameter.getNestedParameterType().getSimpleName(); + String typeName = this.parameter.getNestedParameterType().getSimpleName(); + return "Required request header '" + this.headerName + "' for method parameter type " + typeName + " is " + + (isMissingAfterConversion() ? "present but converted to null" : "not present"); } /** diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestValueException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestValueException.java new file mode 100644 index 000000000000..1de083e3a8b5 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestValueException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.bind; + +/** + * Base class for {@link ServletRequestBindingException} exceptions that could + * not bind because the request value is required but is either missing or + * otherwise resolves to {@code null} after conversion. + * + * @author Rossen Stoyanchev + * @since 5.3.6 + */ +@SuppressWarnings("serial") +public class MissingRequestValueException extends ServletRequestBindingException { + + private final boolean missingAfterConversion; + + + public MissingRequestValueException(String msg) { + this(msg, false); + } + + public MissingRequestValueException(String msg, boolean missingAfterConversion) { + super(msg); + this.missingAfterConversion = missingAfterConversion; + } + + + /** + * Whether the request value was present but converted to {@code null}, e.g. via + * {@code org.springframework.core.convert.support.IdToEntityConverter}. + */ + public boolean isMissingAfterConversion() { + return this.missingAfterConversion; + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java index 67feef6bfa84..8bab4da052e2 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ * @since 2.0.2 */ @SuppressWarnings("serial") -public class MissingServletRequestParameterException extends ServletRequestBindingException { +public class MissingServletRequestParameterException extends MissingRequestValueException { private final String parameterName; @@ -36,7 +36,20 @@ public class MissingServletRequestParameterException extends ServletRequestBindi * @param parameterType the expected type of the missing parameter */ public MissingServletRequestParameterException(String parameterName, String parameterType) { - super(""); + this(parameterName, parameterType, false); + } + + /** + * Constructor for use when a value was present but converted to {@code null}. + * @param parameterName the name of the missing parameter + * @param parameterType the expected type of the missing parameter + * @param missingAfterConversion whether the value became null after conversion + * @since 5.3.6 + */ + public MissingServletRequestParameterException( + String parameterName, String parameterType, boolean missingAfterConversion) { + + super("", missingAfterConversion); this.parameterName = parameterName; this.parameterType = parameterType; } @@ -44,7 +57,9 @@ public MissingServletRequestParameterException(String parameterName, String para @Override public String getMessage() { - return "Required " + this.parameterType + " parameter '" + this.parameterName + "' is not present"; + return "Required request parameter '" + this.parameterName + "' for method parameter type " + + this.parameterType + " is " + + (isMissingAfterConversion() ? "present but converted to null" : "not present"); } /** diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java index 99103719f53d..d1f28393aa74 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractCookieValueMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.context.request.NativeWebRequest; /** * A base abstract class to resolve method arguments annotated with @@ -70,6 +71,12 @@ protected void handleMissingValue(String name, MethodParameter parameter) throws throw new MissingRequestCookieException(name, parameter); } + @Override + protected void handleMissingValueAfterConversion( + String name, MethodParameter parameter, NativeWebRequest request) throws Exception { + + throw new MissingRequestCookieException(name, parameter, true); + } private static final class CookieValueNamedValueInfo extends NamedValueInfo { diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index c389b6f9c7e8..4e0c8095c608 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,7 +135,7 @@ else if ("".equals(arg) && namedValueInfo.defaultValue != null) { // Check for null value after conversion of incoming argument value if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) { - handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); + handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest); } } @@ -237,6 +237,19 @@ protected void handleMissingValue(String name, MethodParameter parameter) throws "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName()); } + /** + * Invoked when a named value is present but becomes {@code null} after conversion. + * @param name the name for the value + * @param parameter the method parameter + * @param request the current request + * @since 5.3.6 + */ + protected void handleMissingValueAfterConversion(String name, MethodParameter parameter, NativeWebRequest request) + throws Exception { + + handleMissingValue(name, parameter, request); + } + /** * A {@code null} results in a {@code false} value for {@code boolean}s or an exception for other primitives. */ diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java index 85b856bf43c2..bfc79c787bba 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,6 +87,12 @@ protected void handleMissingValue(String name, MethodParameter parameter) throws throw new MissingRequestHeaderException(name, parameter); } + @Override + protected void handleMissingValueAfterConversion( + String name, MethodParameter parameter, NativeWebRequest request) throws Exception { + + throw new MissingRequestHeaderException(name, parameter, true); + } private static final class RequestHeaderNamedValueInfo extends NamedValueInfo { diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java index 22e1bc3a5eb9..e1d373ca5b81 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -190,6 +190,20 @@ protected Object resolveName(String name, MethodParameter parameter, NativeWebRe protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { + handleMissingValueInternal(name, parameter, request, false); + } + + @Override + protected void handleMissingValueAfterConversion( + String name, MethodParameter parameter, NativeWebRequest request) throws Exception { + + handleMissingValueInternal(name, parameter, request, true); + } + + protected void handleMissingValueInternal( + String name, MethodParameter parameter, NativeWebRequest request, boolean missingAfterConversion) + throws Exception { + HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { if (servletRequest == null || !MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { @@ -201,7 +215,7 @@ protected void handleMissingValue(String name, MethodParameter parameter, Native } else { throw new MissingServletRequestParameterException(name, - parameter.getNestedParameterType().getSimpleName()); + parameter.getNestedParameterType().getSimpleName(), missingAfterConversion); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java index 8009da6daa27..cba21ac3dfe5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,6 +126,12 @@ protected void handleMissingValue(String name, MethodParameter parameter) throws throw new MissingMatrixVariableException(name, parameter); } + @Override + protected void handleMissingValueAfterConversion( + String name, MethodParameter parameter, NativeWebRequest request) throws Exception { + + throw new MissingMatrixVariableException(name, parameter, true); + } private static final class MatrixVariableNamedValueInfo extends NamedValueInfo { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java index 526eb0355a23..21a9fdb98480 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,6 +101,13 @@ protected void handleMissingValue(String name, MethodParameter parameter) throws throw new MissingPathVariableException(name, parameter); } + @Override + protected void handleMissingValueAfterConversion( + String name, MethodParameter parameter, NativeWebRequest request) throws Exception { + + throw new MissingPathVariableException(name, parameter, true); + } + @Override @SuppressWarnings("unchecked") protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,