@ -17,10 +17,7 @@
@@ -17,10 +17,7 @@
package org.springframework.web.reactive.result.method.annotation ;
import java.lang.annotation.Annotation ;
import java.lang.reflect.Constructor ;
import java.util.List ;
import java.util.Map ;
import java.util.Optional ;
import reactor.core.publisher.Mono ;
import reactor.core.publisher.Sinks ;
@ -31,7 +28,6 @@ import org.springframework.context.i18n.LocaleContextHolder;
@@ -31,7 +28,6 @@ import org.springframework.context.i18n.LocaleContextHolder;
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 ;
@ -100,72 +96,74 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
@@ -100,72 +96,74 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
public Mono < Object > resolveArgument (
MethodParameter parameter , BindingContext context , ServerWebExchange exchange ) {
ResolvableType type = ResolvableType . forMethodParameter ( parameter ) ;
Class < ? > resolvedType = type . resolve ( ) ;
ReactiveAdapter adapter = ( resolvedType ! = null ? getAdapterRegistry ( ) . getAdapter ( resolvedType ) : null ) ;
ResolvableType valueType = ( adapter ! = null ? type . getGeneric ( ) : type ) ;
Assert . state ( adapter = = null | | ! adapter . isMultiValue ( ) ,
( ) - > getClass ( ) . getSimpleName ( ) + " does not support multi-value reactive type wrapper: " +
parameter . getGenericParameterType ( ) ) ;
Class < ? > resolvedType = parameter . getParameterType ( ) ;
ReactiveAdapter adapter = getAdapterRegistry ( ) . getAdapter ( resolvedType ) ;
Assert . state ( adapter = = null | | ! adapter . isMultiValue ( ) , "Multi-value publisher is not supported" ) ;
String name = ModelInitializer . getNameForParameter ( parameter ) ;
Mono < ? > attributeMono = prepareAttributeMono ( name , valueType , context , exchange ) ;
// unsafe(): we're intercepting, already serialized Publisher signals
Mono < WebExchangeDataBinder > dataBinderMono = initDataBinder (
name , ( adapter ! = null ? parameter . nested ( ) : parameter ) , context , exchange ) ;
// unsafe() is OK: source is Reactive Streams Publisher
Sinks . One < BindingResult > bindingResultSink = Sinks . unsafe ( ) . one ( ) ;
Map < String , Object > model = context . getModel ( ) . asMap ( ) ;
model . put ( BindingResult . MODEL_KEY_PREFIX + name , bindingResultSink . asMono ( ) ) ;
return attributeMono . flatMap ( attribute - > {
WebExchangeDataBinder binder = context . createDataBinder ( exchange , attribute , name , parameter ) ;
return ( ! bindingDisabled ( parameter ) ? bindRequestParameters ( binder , exchange ) : Mono . empty ( ) )
. doOnError ( bindingResultSink : : tryEmitError )
. doOnSuccess ( aVoid - > {
validateIfApplicable ( binder , parameter , exchange ) ;
BindingResult bindingResult = binder . getBindingResult ( ) ;
model . put ( BindingResult . MODEL_KEY_PREFIX + name , bindingResult ) ;
model . put ( name , attribute ) ;
// Ignore result: serialized and buffered (should never fail)
bindingResultSink . tryEmitValue ( bindingResult ) ;
} )
. then ( Mono . fromCallable ( ( ) - > {
BindingResult errors = binder . getBindingResult ( ) ;
if ( adapter ! = null ) {
return adapter . fromPublisher ( errors . hasErrors ( ) ?
Mono . error ( new WebExchangeBindException ( parameter , errors ) ) : attributeMono ) ;
}
else {
if ( errors . hasErrors ( ) & & ! hasErrorsArgument ( parameter ) ) {
throw new WebExchangeBindException ( parameter , errors ) ;
}
return attribute ;
}
} ) ) ;
} ) ;
return dataBinderMono
. flatMap ( binder - > {
Object attribute = binder . getTarget ( ) ;
Assert . state ( attribute ! = null , "Expected model attribute instance" ) ;
return ( ! bindingDisabled ( parameter ) ? bindRequestParameters ( binder , exchange ) : Mono . empty ( ) )
. doOnError ( bindingResultSink : : tryEmitError )
. doOnSuccess ( aVoid - > {
validateIfApplicable ( binder , parameter , exchange ) ;
BindingResult bindingResult = binder . getBindingResult ( ) ;
model . put ( BindingResult . MODEL_KEY_PREFIX + name , bindingResult ) ;
model . put ( name , attribute ) ;
// Ignore result: serialized and buffered (should never fail)
bindingResultSink . tryEmitValue ( bindingResult ) ;
} )
. then ( Mono . fromCallable ( ( ) - > {
BindingResult errors = binder . getBindingResult ( ) ;
if ( adapter ! = null ) {
Mono < Object > mono = ( errors . hasErrors ( ) ?
Mono . error ( new WebExchangeBindException ( parameter , errors ) ) :
Mono . just ( attribute ) ) ;
return adapter . fromPublisher ( mono ) ;
}
else {
if ( errors . hasErrors ( ) & & ! hasErrorsArgument ( parameter ) ) {
throw new WebExchangeBindException ( parameter , errors ) ;
}
return attribute ;
}
} ) ) ;
} ) ;
}
private Mono < ? > prepareAttributeMono (
String name , ResolvableType type , BindingContext context , ServerWebExchange exchange ) {
Object attribute = context . getModel ( ) . asMap ( ) . get ( name ) ;
private Mono < WebExchangeDataBinder > initDataBinder (
String name , MethodParameter parameter , BindingContext context , ServerWebExchange exchange ) {
if ( attribute = = null ) {
attribute = removeReactiveAttribute ( context . getModel ( ) , name ) ;
Object value = context . getModel ( ) . asMap ( ) . get ( name ) ;
if ( value = = null ) {
value = removeReactiveAttribute ( name , context . getModel ( ) ) ;
}
if ( attribute = = null ) {
return createAttribute ( name , type . toClass ( ) , context , exchange ) ;
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 ) ) ;
}
else {
WebExchangeDataBinder binder = context . createDataBinder ( exchange , null , name , parameter ) ;
return constructAttribute ( binder , exchange ) . thenReturn ( binder ) ;
}
ReactiveAdapter adapter = getAdapterRegistry ( ) . getAdapter ( null , attribute ) ;
Assert . isTrue ( adapter = = null | | ! adapter . isMultiValue ( ) , "Model attribute must be single-value publisher" ) ;
return ( adapter ! = null ? Mono . from ( adapter . toPublisher ( attribute ) ) : Mono . justOrEmpty ( attribute ) ) ;
}
@Nullable
private Object removeReactiveAttribute ( Model model , String name ) {
private Object removeReactiveAttribute ( String name , Model model ) {
for ( Map . Entry < String , Object > entry : model . asMap ( ) . entrySet ( ) ) {
if ( entry . getKey ( ) . startsWith ( name ) ) {
ReactiveAdapter adapter = getAdapterRegistry ( ) . getAdapter ( null , entry . getValue ( ) ) ;
@ -181,66 +179,24 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
@@ -181,66 +179,24 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
return null ;
}
private Mono < ? > createAttribute (
String attributeName , Class < ? > clazz , BindingContext context , ServerWebExchange exchange ) {
Constructor < ? > ctor = BeanUtils . getResolvableConstructor ( clazz ) ;
return constructAttribute ( ctor , attributeName , context , exchange ) ;
}
private Mono < ? > constructAttribute ( Constructor < ? > ctor , String attributeName ,
BindingContext context , ServerWebExchange exchange ) {
if ( ctor . getParameterCount ( ) = = 0 ) {
// A single default constructor -> clearly a standard JavaBeans arrangement.
return Mono . just ( BeanUtils . instantiateClass ( ctor ) ) ;
}
// A single data class constructor -> resolve constructor arguments from request parameters.
WebExchangeDataBinder binder = context . createDataBinder ( exchange , null , attributeName ) ;
return getValuesToBind ( binder , exchange ) . map ( bindValues - > {
String [ ] paramNames = BeanUtils . getParameterNames ( ctor ) ;
Class < ? > [ ] paramTypes = ctor . getParameterTypes ( ) ;
Object [ ] args = new Object [ paramTypes . length ] ;
String fieldDefaultPrefix = binder . getFieldDefaultPrefix ( ) ;
String fieldMarkerPrefix = binder . getFieldMarkerPrefix ( ) ;
for ( int i = 0 ; i < paramNames . length ; i + + ) {
String paramName = paramNames [ i ] ;
Class < ? > paramType = paramTypes [ i ] ;
Object value = bindValues . get ( paramName ) ;
if ( value = = null ) {
if ( fieldDefaultPrefix ! = null ) {
value = bindValues . get ( fieldDefaultPrefix + paramName ) ;
}
if ( value = = null & & fieldMarkerPrefix ! = null ) {
if ( bindValues . get ( fieldMarkerPrefix + paramName ) ! = null ) {
value = binder . getEmptyValue ( paramType ) ;
}
}
}
value = ( value instanceof List < ? > list ? list . toArray ( ) : value ) ;
MethodParameter methodParam = MethodParameter . forFieldAwareConstructor ( ctor , i , paramName ) ;
if ( value = = null & & methodParam . isOptional ( ) ) {
args [ i ] = ( methodParam . getParameterType ( ) = = Optional . class ? Optional . empty ( ) : null ) ;
}
else {
args [ i ] = binder . convertIfNecessary ( value , paramTypes [ i ] , methodParam ) ;
}
}
return BeanUtils . instantiateClass ( ctor , args ) ;
} ) ;
/ * *
* Protected method to obtain the values for data binding .
* @deprecated and not called ; replaced by built - in support for
* constructor initialization in { @link org . springframework . validation . DataBinder }
* /
@Deprecated ( since = "6.1" , forRemoval = true )
public Mono < Map < String , Object > > getValuesToBind ( WebExchangeDataBinder binder , ServerWebExchange exchange ) {
throw new UnsupportedOperationException ( ) ;
}
/ * *
* Protected method to obtain the values for data binding . By default this
* method delegates to { @link WebExchangeDataBinder # getValuesToBind } .
* @param binder the data binder in use
* Extension point to create the attribute , binding the request to constructor args .
* @param binder the data binder instance to use for the binding
* @param exchange the current exchange
* @return a map of bind values
* @since 5 . 3
* @since 6 . 1
* /
public Mono < Map < String , Object > > getValuesToBind ( WebExchangeDataBinder binder , ServerWebExchange exchange ) {
return binder . getValuesToBind ( exchange ) ;
protected Mono < Void > constructAttribute ( WebExchangeDataBinder binder , ServerWebExchange exchange ) {
return binder . construct ( exchange ) ;
}
/ * *