Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
poutsma committed Sep 22, 2021
1 parent 345befd commit 2d03633
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
import jakarta.servlet.http.Part;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
Expand Down Expand Up @@ -62,6 +62,7 @@
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Arjen Poutsma
* @see #bind(jakarta.servlet.ServletRequest)
* @see #registerCustomEditor
* @see #setAllowedFields
Expand Down Expand Up @@ -148,8 +149,8 @@ public void closeNoCatch() throws ServletRequestBindingException {
}
}

public <T> T construct(ServletRequest request, Constructor<T> ctor, Callback callback, @Nullable MethodParameter parameter) throws Exception {
return super.construct(ctor, (name, type) -> getBindValue(request, name, type), callback, parameter);
public <T> T construct(ServletRequest request, Constructor<T> ctor, @Nullable MethodParameter parameter) throws Exception {
return super.construct(ctor, (name, type) -> getBindValue(request, name, type), parameter);
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.Errors;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

/**
Expand All @@ -60,6 +65,7 @@
* @author Juergen Hoeller
* @author Scott Andrews
* @author Brian Clozel
* @author Arjen Poutsma
* @since 1.2
* @see #registerCustomEditor
* @see #setAllowedFields
Expand Down Expand Up @@ -368,7 +374,7 @@ protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, Mu

@SuppressWarnings("serial")
protected <T> T construct(Constructor<T> ctor, BiFunction<String, Class<?>, Object> values,
@Nullable Callback callback, @Nullable MethodParameter parameter) throws Exception {
@Nullable MethodParameter parameter) throws Exception {

// A single data class constructor -> resolve constructor arguments from request parameters.
String[] paramNames = BeanUtils.getParameterNames(ctor);
Expand Down Expand Up @@ -399,6 +405,9 @@ protected <T> T construct(Constructor<T> ctor, BiFunction<String, Class<?>, Obje
}
}
}
if (value != null && value.getClass().isArray() && Array.getLength(value) == 1) {
value = Array.get(value, 0);
}
try {
MethodParameter methodParam = new FieldAwareConstructorParameter(ctor, i, paramName);
if (value == null && methodParam.isOptional()) {
Expand All @@ -425,8 +434,8 @@ protected <T> T construct(Constructor<T> ctor, BiFunction<String, Class<?>, Obje
if (!failedParams.contains(paramName)) {
Object value = args[i];
result.recordFieldValue(paramName, paramTypes[i], value);
if (parameter != null && callback != null) {
callback.validateValue(this, parameter, ctor.getDeclaringClass(), paramName, value);
if (parameter != null) {
validateValueIfApplicable(parameter, ctor.getDeclaringClass(), paramName, value);
}
}
}
Expand All @@ -450,9 +459,51 @@ public Object getTarget() {
return BeanUtils.instantiateClass(ctor, args);
}

public interface Callback {
/**
* Validate the specified candidate value if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param parameter the method parameter declaration
* @param targetType the target type
* @param fieldName the name of the field
* @param value the candidate value
* @since 5.1
* @see SmartValidator#validateValue(Class, String, Object, Errors, Object...)
*/
private void validateValueIfApplicable(MethodParameter parameter, Class<?> targetType, String fieldName,
@Nullable Object value) {

for (Annotation ann : parameter.getParameterAnnotations()) {
Object[] validationHints = determineValidationHints(ann);
if (validationHints != null) {
for (Validator validator : getValidators()) {
if (validator instanceof SmartValidator) {
try {
((SmartValidator) validator).validateValue(targetType, fieldName, value,
getBindingResult(), validationHints);
}
catch (IllegalArgumentException ex) {
// No corresponding field on the target class...
}
}
}
break;
}
}
}

void validateValue(WebDataBinder dataBinder, MethodParameter parameter, Class<?> declaringClass, String paramName, Object value);
@Nullable
private Object[] determineValidationHints(Annotation ann) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
if (hints == null) {
return new Object[0];
}
return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
}
return null;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 5.0
*/
public class WebExchangeDataBinder extends WebDataBinder {
Expand Down Expand Up @@ -87,11 +88,10 @@ public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
return extractValuesToBind(exchange);
}

public <T> Mono<T> construct(ServerWebExchange exchange, Constructor<T> ctor,
@Nullable MethodParameter parameter) {
public <T> Mono<T> construct(ServerWebExchange exchange, Constructor<T> ctor, @Nullable MethodParameter parameter) {
return getValuesToBind(exchange).flatMap(bindValues -> {
try {
return Mono.just(super.construct(ctor, (name, type) -> bindValues.get(name), null, parameter));
return Mono.just(super.construct(ctor, (name, type) -> bindValues.get(name), parameter));
}
catch (Exception ex) {
return Mono.error(ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@

package org.springframework.web.bind.support;

import java.lang.reflect.Constructor;
import java.util.List;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.Part;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
Expand All @@ -28,6 +33,7 @@
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.multipart.support.StandardServletPartUtils;

Expand Down Expand Up @@ -126,6 +132,39 @@ else if (StringUtils.startsWithIgnoreCase(
doBind(mpvs);
}

public <T> T construct(WebRequest request, Constructor<T> ctor, @Nullable MethodParameter parameter) throws Exception {
return super.construct(ctor, (name, type) -> getBindValue(request, name, type), parameter);
}

@Nullable
protected Object getBindValue(WebRequest request, String name, Class<?> type) {
Object value = request.getParameterValues(name);
if (value != null) {
return value;
}
else if (request instanceof NativeWebRequest) {
NativeWebRequest nativeRequest = (NativeWebRequest) request;
MultipartRequest multipartRequest = nativeRequest.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
return (files.size() == 1 ? files.get(0) : files);
}
}
else if (StringUtils.startsWithIgnoreCase(
request.getHeader(HttpHeaders.CONTENT_TYPE), MediaType.MULTIPART_FORM_DATA_VALUE)) {
HttpServletRequest servletRequest = nativeRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && HttpMethod.POST.matches(servletRequest.getMethod())) {
List<Part> parts = StandardServletPartUtils.getParts(servletRequest, name);
if (!parts.isEmpty()) {
return (parts.size() == 1 ? parts.get(0) : parts);
}
}
}
}
return null;
}

/**
* Treats errors as fatal.
* <p>Use this method only if it's an error if the input isn't valid.
Expand All @@ -138,4 +177,5 @@ public void closeNoCatch() throws BindException {
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -242,17 +242,12 @@ protected Object constructAttribute(Constructor<?> ctor, String attributeName, M
ServletRequestDataBinder servletRequestDataBinder = (ServletRequestDataBinder) binder;
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null) {
return servletRequestDataBinder.construct(servletRequest, ctor, new WebDataBinder.Callback() {
@Override
public void validateValue(WebDataBinder dataBinder, MethodParameter parameter, Class<?> declaringClass,
String paramName, Object value) {
validateValueIfApplicable(dataBinder, parameter, declaringClass, paramName, value);
}
}, parameter);
return servletRequestDataBinder.construct(servletRequest, ctor, parameter);
}
}
else if (binder instanceof WebRequestDataBinder) {
// TODO?
WebRequestDataBinder webRequestDataBinder = (WebRequestDataBinder) binder;
return webRequestDataBinder.construct(webRequest, ctor, parameter);
}
return null;
}
Expand Down Expand Up @@ -325,6 +320,7 @@ protected void validateIfApplicable(WebDataBinder binder, MethodParameter parame
* @see #validateIfApplicable(WebDataBinder, MethodParameter)
* @see SmartValidator#validateValue(Class, String, Object, Errors, Object...)
*/
@Deprecated
protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter parameter,
Class<?> targetType, String fieldName, @Nullable Object value) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ public void resolveConstructorListArgumentFromCommaSeparatedRequestParameter() t
});

Object resolved = this.processor.resolveArgument(this.beanWithConstructorArgs, this.container, requestWithParam, factory);
assertThat(resolved).isNotNull();
assertThat(resolved).isInstanceOf(TestBeanWithConstructorArgs.class);
assertThat(((TestBeanWithConstructorArgs) resolved).listOfStrings).containsExactly("1", "2");
}
Expand Down

0 comments on commit 2d03633

Please sign in to comment.