Skip to content

Commit

Permalink
Use ResolvableType to create WebDataBinder
Browse files Browse the repository at this point in the history
This provides more flexibility to pass a targetType even if
a MethodParameter is not available.

See gh-26721
  • Loading branch information
rstoyanchev committed Jun 26, 2023
1 parent d37d668 commit 11a4161
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,30 @@ public final WebDataBinder createBinder(
@Override
public final WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName,
MethodParameter parameter) throws Exception {
ResolvableType type) throws Exception {

return createBinderInternal(webRequest, target, objectName, parameter);
return createBinderInternal(webRequest, target, objectName, type);
}

private WebDataBinder createBinderInternal(
NativeWebRequest webRequest, @Nullable Object target, String objectName,
@Nullable MethodParameter parameter) throws Exception {
@Nullable ResolvableType type) throws Exception {

WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);

if (target == null && parameter != null) {
dataBinder.setTargetType(ResolvableType.forMethodParameter(parameter));
if (target == null && type != null) {
dataBinder.setTargetType(type);
}

if (this.initializer != null) {
this.initializer.initBinder(dataBinder);
}
initBinder(dataBinder, webRequest);

if (this.methodValidationApplicable && parameter != null) {
MethodValidationInitializer.initBinder(dataBinder, parameter);
if (this.methodValidationApplicable && type != null) {
if (type.getSource() instanceof MethodParameter parameter) {
MethodValidationInitializer.initBinder(dataBinder, parameter);
}
}

return dataBinder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package org.springframework.web.bind.support;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
Expand Down Expand Up @@ -44,13 +44,14 @@ WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target,

/**
* Variant of {@link #createBinder(NativeWebRequest, Object, String)} with a
* {@link MethodParameter} for which the {@code DataBinder} is created. This
* may provide more insight to initialize the {@link WebDataBinder}.
* {@link ResolvableType} for which the {@code DataBinder} is created.
* This may be used to construct the target, or otherwise provide more
* insight on how to initialize the binder.
* @since 6.1
*/
default WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName,
MethodParameter parameter) throws Exception {
ResolvableType targetType) throws Exception {

return createBinder(webRequest, target, objectName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,12 @@ protected static void addBindValue(Map<String, Object> params, String key, List<
/**
* Resolve values from a map.
*/
private static class MapValueResolver implements ValueResolver {

private final Map<String, Object> map;

private MapValueResolver(Map<String, Object> map) {
this.map = map;
}
private record MapValueResolver(Map<String, Object> map) implements ValueResolver {

@Override
public Object resolveValue(String name, Class<?> type) {
return this.map.get(name);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
Expand Down Expand Up @@ -142,7 +143,8 @@ public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAn

// No BindingResult yet, proceed with binding and validation
if (bindingResult == null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name, parameter);
ResolvableType type = ResolvableType.forMethodParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name, type);
if (attribute == null) {
constructAttribute(binder, webRequest);
attribute = wrapAsOptionalIfNecessary(parameter, binder.getTarget());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ public void resolveArgumentValidation() throws Exception {

StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
WebDataBinderFactory factory = mock();
given(factory.createBinder(this.request, target, name, this.paramNamedValidModelAttr)).willReturn(dataBinder);
ResolvableType type = ResolvableType.forMethodParameter(this.paramNamedValidModelAttr);
given(factory.createBinder(this.request, target, name, type)).willReturn(dataBinder);

this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);

Expand All @@ -198,7 +199,8 @@ public void resolveArgumentBindingDisabledPreviously() throws Exception {

StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
WebDataBinderFactory factory = mock();
given(factory.createBinder(this.request, target, name, this.paramNamedValidModelAttr)).willReturn(dataBinder);
ResolvableType type = ResolvableType.forMethodParameter(this.paramNamedValidModelAttr);
given(factory.createBinder(this.request, target, name, type)).willReturn(dataBinder);

this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);

Expand All @@ -214,7 +216,8 @@ public void resolveArgumentBindingDisabled() throws Exception {

StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
WebDataBinderFactory factory = mock();
given(factory.createBinder(this.request, target, name, this.paramBindingDisabledAttr)).willReturn(dataBinder);
ResolvableType type = ResolvableType.forMethodParameter(this.paramBindingDisabledAttr);
given(factory.createBinder(this.request, target, name, type)).willReturn(dataBinder);

this.processor.resolveArgument(this.paramBindingDisabledAttr, this.container, this.request, factory);

Expand All @@ -232,12 +235,13 @@ public void resolveArgumentBindException() throws Exception {
dataBinder.getBindingResult().reject("error");

WebDataBinderFactory binderFactory = mock();
given(binderFactory.createBinder(this.request, target, name, this.paramNonSimpleType)).willReturn(dataBinder);
ResolvableType type = ResolvableType.forMethodParameter(this.paramNonSimpleType);
given(binderFactory.createBinder(this.request, target, name, type)).willReturn(dataBinder);

assertThatExceptionOfType(MethodArgumentNotValidException.class).isThrownBy(() ->
this.processor.resolveArgument(this.paramNonSimpleType, this.container, this.request, binderFactory));

verify(binderFactory).createBinder(this.request, target, name, this.paramNonSimpleType);
verify(binderFactory).createBinder(this.request, target, name, type);
}

@Test // SPR-9378
Expand All @@ -252,7 +256,8 @@ public void resolveArgumentOrdering() throws Exception {

StubRequestDataBinder dataBinder = new StubRequestDataBinder(testBean, name);
WebDataBinderFactory binderFactory = mock();
given(binderFactory.createBinder(this.request, testBean, name, this.paramModelAttr)).willReturn(dataBinder);
ResolvableType type = ResolvableType.forMethodParameter(this.paramModelAttr);
given(binderFactory.createBinder(this.request, testBean, name, type)).willReturn(dataBinder);

this.processor.resolveArgument(this.paramModelAttr, this.container, this.request, binderFactory);

Expand Down Expand Up @@ -301,10 +306,11 @@ private void testGetAttributeFromModel(String expectedAttrName, MethodParameter

WebDataBinder dataBinder = new WebRequestDataBinder(target);
WebDataBinderFactory factory = mock();
given(factory.createBinder(this.request, target, expectedAttrName, param)).willReturn(dataBinder);
ResolvableType type = ResolvableType.forMethodParameter(param);
given(factory.createBinder(this.request, target, expectedAttrName, type)).willReturn(dataBinder);

this.processor.resolveArgument(param, this.container, this.request, factory);
verify(factory).createBinder(this.request, target, expectedAttrName, param);
verify(factory).createBinder(this.request, target, expectedAttrName, type);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,26 +116,28 @@ public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, String
}

/**
* Create a binder with a target object and a {@code MethodParameter}.
* Create a binder with a target object and a {@link ResolvableType targetType}.
* If the target is {@code null}, then
* {@link WebExchangeDataBinder#setTargetType targetType} is set.
* @since 6.1
*/
public WebExchangeDataBinder createDataBinder(
ServerWebExchange exchange, @Nullable Object target, String name, @Nullable MethodParameter parameter) {
ServerWebExchange exchange, @Nullable Object target, String name, @Nullable ResolvableType targetType) {

WebExchangeDataBinder dataBinder = new ExtendedWebExchangeDataBinder(target, name);
if (target == null && parameter != null) {
dataBinder.setTargetType(ResolvableType.forMethodParameter(parameter));
if (target == null && targetType != null) {
dataBinder.setTargetType(targetType);
}

if (this.initializer != null) {
this.initializer.initBinder(dataBinder);
}
dataBinder = initDataBinder(dataBinder, exchange);

if (this.methodValidationApplicable && parameter != null) {
MethodValidationInitializer.initBinder(dataBinder, parameter);
if (this.methodValidationApplicable && targetType != null) {
if (targetType.getSource() instanceof MethodParameter parameter) {
MethodValidationInitializer.initBinder(dataBinder, parameter);
}
}

return dataBinder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,12 @@ private Object[] extractValidationHints(MethodParameter parameter) {
return null;
}

private void validate(Object target, Object[] validationHints, MethodParameter param,
private void validate(Object target, Object[] validationHints, MethodParameter parameter,
BindingContext binding, ServerWebExchange exchange) {

String name = Conventions.getVariableNameForParameter(param);
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name, param);
String name = Conventions.getVariableNameForParameter(parameter);
ResolvableType type = ResolvableType.forMethodParameter(parameter);
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name, type);
try {
LocaleContextHolder.setLocaleContext(exchange.getLocaleContext());
binder.validate(validationHints);
Expand All @@ -278,7 +279,7 @@ private void validate(Object target, Object[] validationHints, MethodParameter p
LocaleContextHolder.resetLocaleContext();
}
if (binder.getBindingResult().hasErrors()) {
throw new WebExchangeBindException(param, binder.getBindingResult());
throw new WebExchangeBindException(parameter, binder.getBindingResult());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -150,14 +151,15 @@ private Mono<WebExchangeDataBinder> initDataBinder(
if (value == null) {
value = removeReactiveAttribute(name, context.getModel());
}
ResolvableType type = ResolvableType.forMethodParameter(parameter);
if (value != null) {
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(null, value);
Assert.isTrue(adapter == null || !adapter.isMultiValue(), "Multi-value publisher is not supported");
return (adapter != null ? Mono.from(adapter.toPublisher(value)) : Mono.just(value))
.map(attr -> context.createDataBinder(exchange, attr, name, parameter));
.map(attr -> context.createDataBinder(exchange, attr, name, type));
}
else {
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, name, parameter);
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, name, type);
return constructAttribute(binder, exchange).thenReturn(binder);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import jakarta.servlet.http.HttpServletRequest;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -139,7 +140,8 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(request, arg, name, parameter);
ResolvableType type = ResolvableType.forMethodParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(request, arg, name, type);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;
Expand Down Expand Up @@ -134,7 +135,8 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC
String name = Conventions.getVariableNameForParameter(parameter);

if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name, parameter);
ResolvableType type = ResolvableType.forMethodParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name, type);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
Expand Down

0 comments on commit 11a4161

Please sign in to comment.