|
|
|
@ -22,10 +22,12 @@ import java.io.PushbackInputStream; |
|
|
|
import java.lang.annotation.Annotation; |
|
|
|
import java.lang.annotation.Annotation; |
|
|
|
import java.lang.reflect.Type; |
|
|
|
import java.lang.reflect.Type; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
|
|
|
|
import java.util.Collection; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.EnumSet; |
|
|
|
import java.util.EnumSet; |
|
|
|
import java.util.LinkedHashSet; |
|
|
|
import java.util.LinkedHashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
import java.util.Optional; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.Set; |
|
|
|
import javax.servlet.http.HttpServletRequest; |
|
|
|
import javax.servlet.http.HttpServletRequest; |
|
|
|
|
|
|
|
|
|
|
|
@ -59,6 +61,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; |
|
|
|
* |
|
|
|
* |
|
|
|
* @author Arjen Poutsma |
|
|
|
* @author Arjen Poutsma |
|
|
|
* @author Rossen Stoyanchev |
|
|
|
* @author Rossen Stoyanchev |
|
|
|
|
|
|
|
* @author Juergen Hoeller |
|
|
|
* @since 3.1 |
|
|
|
* @since 3.1 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver { |
|
|
|
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver { |
|
|
|
@ -128,17 +131,17 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
* reading from the given request. |
|
|
|
* reading from the given request. |
|
|
|
* @param <T> the expected type of the argument value to be created |
|
|
|
* @param <T> the expected type of the argument value to be created |
|
|
|
* @param webRequest the current request |
|
|
|
* @param webRequest the current request |
|
|
|
* @param methodParam the method argument |
|
|
|
* @param parameter the method parameter descriptor (may be {@code null}) |
|
|
|
* @param paramType the type of the argument value to be created |
|
|
|
* @param paramType the type of the argument value to be created |
|
|
|
* @return the created method argument value |
|
|
|
* @return the created method argument value |
|
|
|
* @throws IOException if the reading from the request fails |
|
|
|
* @throws IOException if the reading from the request fails |
|
|
|
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found |
|
|
|
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, |
|
|
|
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, |
|
|
|
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { |
|
|
|
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { |
|
|
|
|
|
|
|
|
|
|
|
HttpInputMessage inputMessage = createInputMessage(webRequest); |
|
|
|
HttpInputMessage inputMessage = createInputMessage(webRequest); |
|
|
|
return readWithMessageConverters(inputMessage, methodParam, paramType); |
|
|
|
return readWithMessageConverters(inputMessage, parameter, paramType); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -146,7 +149,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
* from the given HttpInputMessage. |
|
|
|
* from the given HttpInputMessage. |
|
|
|
* @param <T> the expected type of the argument value to be created |
|
|
|
* @param <T> the expected type of the argument value to be created |
|
|
|
* @param inputMessage the HTTP input message representing the current request |
|
|
|
* @param inputMessage the HTTP input message representing the current request |
|
|
|
* @param param the method parameter descriptor (may be {@code null}) |
|
|
|
* @param parameter the method parameter descriptor (may be {@code null}) |
|
|
|
* @param targetType the target type, not necessarily the same as the method |
|
|
|
* @param targetType the target type, not necessarily the same as the method |
|
|
|
* parameter type, e.g. for {@code HttpEntity<String>}. |
|
|
|
* parameter type, e.g. for {@code HttpEntity<String>}. |
|
|
|
* @return the created method argument value |
|
|
|
* @return the created method argument value |
|
|
|
@ -154,7 +157,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found |
|
|
|
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param, |
|
|
|
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, |
|
|
|
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { |
|
|
|
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { |
|
|
|
|
|
|
|
|
|
|
|
MediaType contentType; |
|
|
|
MediaType contentType; |
|
|
|
@ -170,11 +173,11 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
contentType = MediaType.APPLICATION_OCTET_STREAM; |
|
|
|
contentType = MediaType.APPLICATION_OCTET_STREAM; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Class<?> contextClass = (param != null ? param.getContainingClass() : null); |
|
|
|
Class<?> contextClass = (parameter != null ? parameter.getContainingClass() : null); |
|
|
|
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null); |
|
|
|
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null); |
|
|
|
if (targetClass == null) { |
|
|
|
if (targetClass == null) { |
|
|
|
ResolvableType resolvableType = (param != null ? |
|
|
|
ResolvableType resolvableType = (parameter != null ? |
|
|
|
ResolvableType.forMethodParameter(param) : ResolvableType.forType(targetType)); |
|
|
|
ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType)); |
|
|
|
targetClass = (Class<T>) resolvableType.resolve(); |
|
|
|
targetClass = (Class<T>) resolvableType.resolve(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -193,13 +196,12 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); |
|
|
|
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
if (inputMessage.getBody() != null) { |
|
|
|
if (inputMessage.getBody() != null) { |
|
|
|
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType); |
|
|
|
inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); |
|
|
|
body = genericConverter.read(targetType, contextClass, inputMessage); |
|
|
|
body = genericConverter.read(targetType, contextClass, inputMessage); |
|
|
|
body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType); |
|
|
|
body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
body = null; |
|
|
|
body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); |
|
|
|
body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -210,13 +212,12 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); |
|
|
|
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
if (inputMessage.getBody() != null) { |
|
|
|
if (inputMessage.getBody() != null) { |
|
|
|
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType); |
|
|
|
inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); |
|
|
|
body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage); |
|
|
|
body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage); |
|
|
|
body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType); |
|
|
|
body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
body = null; |
|
|
|
body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); |
|
|
|
body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -254,12 +255,12 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
* Spring's {@link org.springframework.validation.annotation.Validated}, |
|
|
|
* Spring's {@link org.springframework.validation.annotation.Validated}, |
|
|
|
* and custom annotations whose name starts with "Valid". |
|
|
|
* and custom annotations whose name starts with "Valid". |
|
|
|
* @param binder the DataBinder to be used |
|
|
|
* @param binder the DataBinder to be used |
|
|
|
* @param methodParam the method parameter |
|
|
|
* @param parameter the method parameter descriptor |
|
|
|
* @see #isBindExceptionRequired |
|
|
|
|
|
|
|
* @since 4.1.5 |
|
|
|
* @since 4.1.5 |
|
|
|
|
|
|
|
* @see #isBindExceptionRequired |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) { |
|
|
|
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { |
|
|
|
Annotation[] annotations = methodParam.getParameterAnnotations(); |
|
|
|
Annotation[] annotations = parameter.getParameterAnnotations(); |
|
|
|
for (Annotation ann : annotations) { |
|
|
|
for (Annotation ann : annotations) { |
|
|
|
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); |
|
|
|
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); |
|
|
|
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { |
|
|
|
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { |
|
|
|
@ -274,17 +275,37 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements |
|
|
|
/** |
|
|
|
/** |
|
|
|
* Whether to raise a fatal bind exception on validation errors. |
|
|
|
* Whether to raise a fatal bind exception on validation errors. |
|
|
|
* @param binder the data binder used to perform data binding |
|
|
|
* @param binder the data binder used to perform data binding |
|
|
|
* @param methodParam the method argument |
|
|
|
* @param parameter the method parameter descriptor |
|
|
|
* @return {@code true} if the next method argument is not of type {@link Errors} |
|
|
|
* @return {@code true} if the next method argument is not of type {@link Errors} |
|
|
|
* @since 4.1.5 |
|
|
|
* @since 4.1.5 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) { |
|
|
|
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) { |
|
|
|
int i = methodParam.getParameterIndex(); |
|
|
|
int i = parameter.getParameterIndex(); |
|
|
|
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes(); |
|
|
|
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes(); |
|
|
|
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); |
|
|
|
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); |
|
|
|
return !hasBindingResult; |
|
|
|
return !hasBindingResult; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Adapt the given argument against the method parameter, if necessary. |
|
|
|
|
|
|
|
* @param arg the resolved argument |
|
|
|
|
|
|
|
* @param parameter the method parameter descriptor |
|
|
|
|
|
|
|
* @return the adapted argument, or the original resolved argument as-is |
|
|
|
|
|
|
|
* @since 4.3.5 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
protected Object adaptArgumentIfNecessary(Object arg, MethodParameter parameter) { |
|
|
|
|
|
|
|
if (parameter.getParameterType() == Optional.class) { |
|
|
|
|
|
|
|
if (arg == null || (arg instanceof Collection && ((Collection) arg).isEmpty()) || |
|
|
|
|
|
|
|
(arg instanceof Object[] && ((Object[]) arg).length == 0)) { |
|
|
|
|
|
|
|
return Optional.empty(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
return Optional.of(arg); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return arg; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage { |
|
|
|
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage { |
|
|
|
|
|
|
|
|
|
|
|
|